Switching up your Spotify experience with microfrontends and Blazor
Tags: blazor, piral, microfrontends, webdev
Published: 2021-05-07 08:03:26

---
title: Switching up your Spotify experience with microfrontends and Blazor
description: "In this article, I will share my experiences creating a microfrontend web app with Blazor and Piral. I'll also give a little behind-the-scenes look at how this was made possible, highlight some of the quirks of using Blazor in a microfrontend solution, and explain how the combination of Piral and Blazor has improved."
tags: blazor, piral, microfrontends, webdev
cover_image: https://i.imgur.com/JqW4EnU.jpg
published: true
---

In [**my previous article**](https://dev.to/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46), I talked about creating a Netflix clone using [Piral](https://piral.io): an open-source framework for creating modular applications. I highly recommend giving that article a quick read first, if you are not yet familiar with microfrontends and/or Piral. 

{% post dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46 %}

Since then, I've been making contributions on GitHub and working closely together with the maintainers of Piral, to provide a helping hand to improve their framework in any way I could. 
The main bulk of the improvements was made in their converter for **Blazor**.

In this article, I will share my experiences creating a microfrontend web app with Blazor and Piral. I'll also give a little *behind-the-scenes* look at how this was made possible, highlight some of the quirks of using Blazor in a microfrontend solution, and explain how the combination of Piral and Blazor has improved.

[![](https://img.shields.io/website?color=1ed45f&style=flat-square&up_message=spiralfy.party&url=https%3A%2F%2Fspiralfy.party)](https://spiralfy.party)

# Contents

- [Spiralfy: a modular web application](#spiralfy-a-modular-web-application)
  * [Overview](#overview)
  * [The Spiralfy appshell](#the-spiralfy-appshell)
  * [The player: a simple pilet](#the-player-a-simple-pilet)
  * [The controls: a Blazor pilet!](#the-controls-a-blazor-pilet)
- [Dev story: making Blazor work with microfrontends](#dev-story-making-blazor-work-with-microfrontends)
  * [Looking back](#looking-back)
  * [What needed improvement, and why?](#what-needed-improvement-and-why)
  * [What has changed and improved?](#what-has-changed-and-improved)
- [Final thoughts](#final-thoughts)


# Spiralfy: a modular web application

<figure>
<center>
<a target="_blank" href="https://spiralfy.party">
<img src="https://i.imgur.com/GM0W1xr.gif">
</a>
</center>
</figure>

<br>

First of all, let's discuss the demo application that I created to showcase the use of Blazor with Piral: Spiralfy. A clever &ndash; or some would say *cheesy* &ndash; play on words between Spotify and Piral, of course. But what does it do?

Log in with your Spotify premium account, and access a way to switch up your Spotify experience!

## Overview

For a long time now, I wanted a way to shuffle play my playlists. I'm not talking about shuffling the songs within one playlist, that's something you can obviously already do. The feature I wanted could be described as "swiping through playlists": Spiralfy picks one playlist at random, shuffle playing its songs, and whenever you feel like you want a different *vibe*, you let Spiralfy pick a new playlist to listen to.

(I got inspired in part by [lofi.cafe](https://lofi.cafe), where you can switch through curated lofi playlists like they were radio stations. But I wanted the user to be able to use their own Spotify playlists instead.)


### The code

> "Talk is cheap. Show me the code" ~ Linus Thorvalds

Spiralfy is a modular distributed web application, also known as a microfrontend application. In my previous microfrontend project I decided to make separate GitHub repositories for each and every module; to really demonstrate that these are highly decoupled and autonomous. This time, I chose to bundle all parts into one repository (a *monorepo*), just because it would be easier to discover and browse all the code at once. You can find the code on [git.io/spiralfy](https://git.io/spiralfy)


{%github DanteDeRuwe/spiralfy %}



For now, Spiralfy exists in 3 parts:
- the Piral instance: `spiralfy-appshell`
- the `player` pilet
- the `controls` pilet

> *What are these words?*
In case you did not read my previous article: a quick summary here. In the Piral framework, pilets are the individual feature modules, also known as microfrontends. Pilets are usually published to a feed service. The Piral instance (aka app shell) will pull all registered pilets from the feed service, and put them where they need to go as defined by the pilets themselves.


## The Spiralfy appshell

For the Spiralfy app shell, I decided to go with `piral-core` instead of the full `piral` framework.

I actually started with the full version of Piral, but after realizing that I will not be using any dashboards, translation, notifications... and other fancy features (bundled in a collection called `piral-ext`) I migrated to `piral-core`.

The [Piral docs page on `piral-core`](https://docs.piral.io/guidelines/tutorials/22-core-and-base) actually describes this scenario pretty well: 
> Quite often the scenario is that somebody starts with `piral` but then realized that one or the other plugin should not be included. (...) In any of these cases a migration from `piral` to `piral-core` makes sense.

There were only 2 plugins that I actually wanted: [`piral-menu`](https://npmjs.com/piral-menu), in case I would want to add items to the navigation menu in an easy way; and [`piral-blazor`](https://npmjs.com/piral-blazor), for reasons explained below.

So, the `index.tsx` file looks a little bit like this:

```jsx
import * as React from 'react';
import { render } from 'react-dom';
import { createBlazorApi } from 'piral-blazor';
import { createInstance, Piral, SetErrors, SetLayout } from 'piral-core';
import { createMenuApi } from 'piral-menu';
import { errors, layout } from './layout';

const feedUrl = 'https://feed.piral.cloud/api/v1/pilet/spiralfy';

const instance = createInstance({
  plugins: [
    createBlazorApi(), //this is where the magic is included ✨
    createMenuApi()
  ],
  requestPilets: () => fetch(feedUrl).then(res => res.json()).then(res => res.items)
});

const piral = (
  <Piral instance={instance}>
    <SetLayout layout={layout} />
    <SetErrors errors={errors} />
  </Piral>
);

render(piral, document.querySelector('#app'));
```

Things to notice about this setup:
- the plugins each come from their respective packages, not from `piral`.
- in the full Piral framework, we would use `renderInstance`. Piral-core however, does not come with react bundled. It means we should use the standard `render` method from `react-dom` to render our Piral instance. (read more [here](https://docs.piral.io/guidelines/tutorials/22-core-and-base))



### [`piral-blazor`](https://npmjs.com/piral-blazor)

Although I called `piral-blazor` a plugin, it is actually considered a **converter**: a package that brings support to use a different UI framework. Piral supports around 15 different UI frameworks, other than React. 

> If you would like more information on how `piral-blazor` works, I would suggest you read the second part of this article, too!

I included the Blazor converter in the app shell, because I already knew I was going to add a Blazor pilet. It's also possible to load `piral-blazor` from a pilet. This is for example useful if the app shell already existed and you don't want to change it. This is beyond the scope of this article.

## The player: a simple pilet

The player pilet is rather barebones. It uses the following npm package, which is neat wrapper around the [Spotify web playback SDK](https://developer.spotify.com/documentation/web-playback-sdk/).

{%github gilbarbara/react-spotify-web-playback no-readme %}

This allows us to have Spotify playback from the browser. If we only used the Spotify API, we could control the playback, but we would have to have an active device it is playing on. This eliminates that.

> Since I wanted to make my own layout for a player in Blazor, I'm setting this one to `display:none`. This way it's loaded, but it's also hidden from view. *Yes I am aware that this is hacky, stop bullying me! :/*

While the pilet is simple, this is however demonstrating a niche problem that is pretty well solved by the fact that Piral (and microfrontends in general) can be very **technology-independent**. If you want to write an app in Blazor, but a certain feature has a pretty nice Javascript library already: You can most of the time just use it from a different microfrontend. This way, there's way less need in having to struggle with any JS interop.


<figure>
<center>
<a target="_blank" href="https://i.imgur.com/DpDlQP9.jpg">
<img src="https://i.imgur.com/DpDlQP9l.png">
</a>
</center>
</figure>


## The controls: a Blazor pilet!

Now for the interesting bit: the Blazor pilet! 

To create the Blazor pilet, I followed the documentation for `Piral.Blazor`, from their [README](https://github.com/smapiot/Piral.Blazor#readme) on GitHub. `Piral.Blazor` is a set of NuGet packages that will make Piral work from the .NET side. 

{%github smapiot/Piral.Blazor no-readme %}

Here is also a quick video on the process, if you don't like reading:

{%youtube 8kWkkNgE3ao %}

The process boils down to installing a template. If you want to transform an existing Blazor app however, all you have to do is defining the app shell name, installing the [`Piral.Blazor.Tools`](https://nuget.org/packages/Piral.Blazor.Tools) package that will create the right files for your pilet, and installing [`Piral.Blazor.Utils`](https://nuget.org/packages/Piral.Blazor.Utils) to be able to use custom Piral attributes in your code.

To make the interaction with the Spotify API a lot easier I used the following great NuGet package, which provides fully typed responses and requests.

{%github JohnnyCrazy/SpotifyAPI-NET no-readme %}


Alright, after this round of *NuGet installing*, we can talk about the components. 

A standard **page** in Blazor, using the `@page` directive, will work as expected, and will be automatically registered on the pilet API. This is what I used to register the player on the homepage.

For an **extension**, like a login button that should end up in the app shell header, we can use the `PiralExtension` attribute, specifying the name of the extension slot you want to render into.

```razor
@attribute [PiralExtension("header-items")]

@if(_username is null){
    <a href="@_authUri">Login via Spotify</a>
}
else
{
    <p>Welcome @_username</p>
}

@code {
   //...
}
```

And... I would say... That's almost the entire story. Because Piral.Blazor does some pretty neat stuff under the hood, the developer experience of creating a Blazor pilet is really similar to creating a regular Blazor WASM application!

### Let's run it!

Because we are using Piral, running the Blazor pilet does include an extra step. We need to use the Piral CLI to do its magic! Again, the `Piral.Blazor` docs to the rescue!

From your Blazor project folder, run your pilet via the Piral CLI :rocket:
```bash
cd ../piral~/<project-name>
npm start
```

(You could also add a `--feed` argument, as outlined [here](https://dev.to/dantederuwe/my-experiences-creating-a-netflix-clone-using-microfrontends-1n46#%E2%96%B8-debugging-one-pilet-and-seeing-the-interaction-with-the-other-pilets-too))

In addition to this, if you want to debug your Blazor pilet using for example Visual Studio, you can just run the pilet with IISExpress. (if you want to use Blazor 3.2, there are extra things to consider. You can read about them [here](https://github.com/smapiot/Piral.Blazor/tree/blazor-3.2#readme). *The cool kids use Blazor 5 anyway* ⌐■_■ )

This way, you can really use the entire Blazor debugging experience: hitting breakpoints, stepping through your code, and so on.




---

<br>
<br>




# Dev story: making Blazor work with microfrontends

*Achievement unlocked: you have reached part 2 of the story.* In this part, I wanted to take the opportunity to tell you about how the combination of Piral and Blazor has matured over the period that I was actively contributing to its codebase; working closely together with the maintainers of Piral. 

## Looking back

Now, how was Piral with Blazor organized some 2 or 3 months ago?


<figure>
<center>
<a target="_blank" href="https://i.imgur.com/kBlzCzA.png">
<img src="https://i.imgur.com/kBlzCzA.png">
<figcaption>The Piral Blazor ecosystem in March, 2021</figcaption>
</a>
</center>
</figure>

This does not look too complicated. Let's break it down.

For the **Piral instance**, we rely of course on the Piral framework (although, as said before, this could also be piral-core). Next to that, we also need `piral-blazor` as a converter. 

Under the hood, `piral-blazor` would actually download the [`Piral.Blazor.Core`](https://www.nuget.org/packages/Piral.Blazor.Core) NuGet package, which would contain 
- Blazor framework files:  dlls, boot files, metadata, etc.
- custom code that would 
    - expose some methods that can be invoked from the JS side to register, load and unload components
    - make sure activated components get rendered in a `<div>` with a specific id
    - provide a way to register dependencies from the pilets in the DI container 
    - ...

`piral-blazor` would include these files in the Piral instance, and could call the exposed methodes using JS interop. This then would allow exposing some functions in the pilet API to allow pilets to activate components from their `setup` function.


The **pilets** would be created using the official template. The template created files that can be divided into 2 categories: 
- the Blazor files
- other files, mainly TS, codegen and JSON files, where the registration with the pilet API was handled (`setup` function etc...)

The Blazor files can be considered to be the heart of the pilet, while the other files were just there to be the proverbial "glue" that would make everything integrate into the Piral framework.

Still confused? Below I give an example on how this all worked.

### Example

Let's say you know how to create the most beautiful counter component in Blazor. You want to use this counter as a microfrontend in your React application, and give it a dedicated page. Maybe you would also want to render it as an extension on another part of your web app.

Of course, you are already using Piral. You would add the `piral-blazor` converter to the plugins of your Piral instance.

Then, you would then set up a pilet using `Piral.Blazor.Template`, and add your counter component. To expose this component to be able to get picked up by Piral you would add an `ExposePilet` attribute. This would look like this:

```razor
@attribute [ExposePilet("my-awesome-counter")]     //!
 
<div>
    <p>Current count: @count</p>
    <button @onclick="Increment">Increment</button>
</div>
 
@code {
    int count = 0;
 
    void Increment()
    {
        count++;
    }
}
```

Then you would edit the created `index.tsx` file to something like:

```typescript
import { PiletApi } from '<name-of-piral-instance>';

export function setup(app: PiletApi) {
  app.defineBlazorReferences([
    require.resolve('./My.Dependency.dll'),
    require.resolve('./My.Components.dll'),
  ])
  app.registerPage('/counter', app.fromBlazor('my-awesome-counter')); //page
  app.registerExtension('counter-slot', app.fromBlazor('my-awesome-counter')); //extension
}
```

These last lines would register the counter component as a page and an extension via the pilet API. `piral-blazor` would then via Piral.Blazor.Core lookup the component in the defined references, activate it using JS invokable methods, and integrate it somewhere in the webpage. 

## What needed improvement, and why?

There are several aspects where the aforementioned ecosystem could improve. Below I outline some of the goals that were set to make this better:


1. The user should be able to select the **version of Blazor** they want, independently of the `piral-blazor` version (because the latter is tied to the Piral version).
2. There should be a way to **transform an existing Blazor application** into a Blazor pilet with minimal effort.
3. **Registering Blazor dependencies** is cumbersome and error-prone: they need to be manually entered, and if they are not in the right order they will not load correctly. Ideally, we would have an automatic solution.
4. **Registering pages and extensions** onto the pilet API should be possible purely from Blazor, without having to do any TypeScript configuration (in the setup function). Additionally, to define pages we should not use a custom attribute, but use the built-in `@page` directive from Blazor.
5. **Debugging** a Blazor pilet from for example Visual Studio should be possible: triggering breakpoints, stepping through the code, ...
6. Various improvements to single-page navigation, static files, scoped razor styles, ...

## What has changed and improved?

Let's dive right in by providing an updated diagram of the Piral Blazor ecosystem:

<figure>
<center>
<a target="_blank" href="https://i.imgur.com/7KWReas.png">
<img src="https://i.imgur.com/7KWReas.png">
<figcaption>The Piral Blazor ecosystem in May, 2021</figcaption>
</a>
</center>
</figure>

Let's go over the improvement goals and see how this new architecture fulfills them: 

> 1\. The user should be able to select the **version of Blazor** they want, independently of the `piral-blazor` version (because the latter is tied to the Piral version).

This first goal was made possible by extracting the responsibility of dealing with Blazor files into a new npm package called `blazor`, and including it as a peer dependency of `piral-blazor`. This way, the user can install a `piral-blazor` version that corresponds to their Piral version, and choose any version of the `blazor` package to include any version of Blazor (e.g. `blazor@3.2.x` will resolve to the .NET Blazor 3.2 release train).

<br>

> 2\. There should be a way to **transform an existing Blazor application** into a Blazor pilet with minimal effort.

For this goal, we created [`Piral.Blazor.Tools`](https://nuget.org/packages/Piral.Blazor.Tools). The workflow for transforming a Blazor project into a pilet now looks something like this: 

1. Add a `PiralInstance` property to your `.csproj` file with the name of your Piral instance. 
2. Install the `Piral.Blazor.Tools` package
3. Build the project. The first time you do this, this can take some time as it will fully scaffold the pilet.

*(stuff omitted for brevity. Read the complete description [here](https://github.com/smapiot/Piral.Blazor#2-transforming-an-existing-blazor-application-into-a-pilet))*

This is the reason the template is now grayed out on the diagram: you don't *need* it anymore (but it's still nice to get a quick start)!

Template or not, the tools package will do all the heavy lifting and create all files needed for integration with the final framework. But this time, they will not be mixed in with the Blazor files: the user should not see these at all! We decided to put them all in a `piral~` folder. This final tilde makes it so that this folder will by default not be checked in, thanks to the default .NET `.gitignore` file.

<br>

> 3\. **Registering Blazor dependencies** is cumbersome and error-prone: they need to be manually entered, and if they are not in the right order they will not load correctly. Ideally, we would have an automatic solution.

The tools package allows us to scaffold the pilet, and also copy over any files that we like to the user's pilet. We used this to fix this problem, by letting the tools copy over a `blazor.codegen` file. 

If you are unfamiliar with codegen files, no worries, I was too! They are basically ways to generate code at build-time. Here you can learn more about one of the loaders that Piral can use (this is one for Parcel):

{%github FlorianRappl/parcel-plugin-codegen no-readme %}

`blazor.codegen` looks for a JSON file called `project.assets.json` to build up a dependency graph for the Blazor project, and then traverses/flattens this graph in the right order: dlls without dependencies (=*leaves* in the graph) should be loaded first, then their parents, and so on.  *(For you algorithm nerds: this is depth-first post-order traversal :nerd_face: )*.

It will then use this ordered list of dependencies to generate the pilet API integration code for us at build-time! (How this works will become clearer together with the next goal)

<br>

> 4\. **Registering pages and extensions** onto the pilet API should be possible purely from Blazor, without having to do any TypeScript configuration (in the setup function). Additionally, to define pages we should not use a custom attribute, but use the built-in `@page` directive from Blazor. 

This was an interesting challenge. What has to happen for this to work is that we should have a list of all components that are defined as pages or extensions. And as an additional challenge: we need this list before the components are even loaded, so we cannot simply get them at runtime.

Luckily, when Blazor gets compiled, this `@page` directive just gets converted into a `[RouteAttribute]`, and we also have the `[PiralExtension]` attribute, so it's all attributes and we can treat these almost in the same way!

What we came up with, is the `Piral.Blazor.Analyzer`: a command line tool that will use reflection on the Blazor project dll to extract all components that have certain attributes. The codegen then calls `dotnet Piral.Blazor.Analyzer <args>`; the output of which gives us something like this:

```json
{
  "routes": ["/counter"],
  "extensions": {
    "My.Components.Counter": ["my-awesome-counter"]
  }
}
```
The codegen then has all the required information to create code for functions that the setup function needs. The `index.tsx` file then will use these generated functions from the codegen, and becomes:

```typescript
import { PiletApi } from '<name-of-piral-instance>';
import * as Blazor from './blazor.codegen';

export function setup(app: PiletApi) {
  Blazor.registerDependencies(app);
  Blazor.registerPages(app);
  Blazor.registerExtensions(app);
  //...
}
```

<br>


> 5\. **Debugging** a Blazor pilet from for example Visual Studio should be possible: triggering  breakpoints, stepping through the code, ...

This was an interesting challenge. The first step for me was learning how the debugger actually works. I stumbled upon an excellent blog post by Safia Abdalla. She's a software engineer at Microsoft on the .NET team.

{%user captainsafia %}

The blogpost can be found on her personal blog: [*Under the hood with debugging in Blazor WebAssembly*](https://safia.rocks/blog/blazor-wasm-debugging/). There, she explains the concept of the debugging proxy.

After some experiments and proof-of-concept work, we found out that in Blazor 3.2, we could not load the dlls and pdbs (symbol files) dynamically. They had to be included in the `blazor.boot.json` file for them to get picked up by the debugger. 
We solved this by configuring the Piral CLI using a kras script injector. I won't go into too much detail here, so I'm oversimplifying massively: [kras](https://github.com/florianrappl/kras) is a proxy/mock server for your frontend. 

{%github florianrappl/kras no-readme %}

While it is most commonly used to mock a backend, we can use it as a proxy too! You can configure kras with js files included in a `mocks` folder. Let's take a look at a code snippet:

```javascript
/* mocks/debug.js */

//...
const toProxy = [...uniqueAssemblyNames, ...uniquePdbNames];
const shouldBeProxied = ({ url }) => toProxy.filter(x => url.endsWith(x))?.length && url.startsWith('/_framework/_bin');

module.exports = (ctx, req, res) => {
    if (shouldBeProxied(req)) {
        return proxy(req, iisUrl);
    } else if (req.url.endsWith('blazor.boot.json')) {
        return returnWithTweakedBBJson(res);
    }
};
```

This last part is what we mentioned before: when we debug we want to modify the `blazor.boot.json` file to include our pilet dlls and pdbs, so they get loaded when the debug starts.

As we see on line 5, a request should be proxied if the url points to one of our unique dlls or pdbs. This means the shared dlls from the appshell will just be loaded as normal, but the dlls the Pilet brings will be proxied. On line 9 we can see where to: the requests for the pilet dlls will be proxied to the url where IISExpress is running (we can get this from the `launchSettings.json` file by the way).

Because of this *hack*, we can now trigger breakpoints in Visual Studio, step through the code, inspect variables, etc.! 

Oh, by the way, .NET 5 made stuff a lot easier with the inclusion of lazy loading! I won't give a full explanation, but it boils down to: we can now just load the pilet dlls and pdbs lazily, and everything just works! Proxying and tweaking the boot file isn't necessary!


<br>



> 6\. Various improvements to single-page navigation, static files, scoped razor styles, ...

I won't go into details about these. There are always things to improve about code, and these were some major ones :) 

### Example

We take the same scenario as before: the counter. With the new setup, if we want a page, there is nothing at all to do that is special. We just need a regular Blazor page. If we also want to expose an extension, we can use the new `[PiralExtension]` attribute from [`Piral.Blazor.Utils`](https://nuget.org/packages/Piral.Blazor.Utils)

```razor
@page "/counter"
@attribute [PiralExtension("my-awesome-counter")] 

<div>
    <p>Current count: @count</p>
    <button @onclick="Increment">Increment</button>
</div>
 
@code {
    int count = 0;
 
    void Increment()
    {
        count++;
    }
}
```

The tooling will do all the rest! **No more screwing around in TypeScript** for a setup function to manually create and keep updated! Isn't that convenient?

*(For more advanced uses you can still define an optional `setup.tsx` file. Refer to the [README](https://github.com/smapiot/Piral.Blazor#readme) to see how that works.)*


<br>

---

<br>


# Final thoughts

My first thought after writing this article would be *"damn, I wrote quite a long article again"*, immediately followed by *"are people actually going to read this one?"*. My last post was received very well, and I thank each and everyone that reacted to it! Feel free to let me know in the comments if this article was in any way interesting, helpful or cool! 

I enjoyed contributing to Piral a lot. I've always wanted to dive into the open-source community, and because of the guidance given by the Piral maintainers, I feel like I could make a difference in this project (Piral is also pretty damn cool if you ask me). 

Looking back on it, in my opinion, using Piral and Blazor has become better in both functionality and developer experience, and I'm really proud of that (*"hey, a Belgian guy that says he is proud of something, that's quite rare!"*). If you want to see my contributions first-hand; or just criticize my code, [here](https://github.com/smapiot/piral/pulls?q=is%3Apr+author%3ADanteDeRuwe) and [here](https://github.com/smapiot/Piral.Blazor/pulls?q=is%3Apr+author%3ADanteDeRuwe) are lists of the PRs I made. 

Then I want to address Blazor. While I can definitely see the appeal of it, and it's a cool technology: it was pretty hard to get a grasp on the technical side of it. Lots of the stuff that's going on is quite *magical* at first. I'm glad the entire thing is open-source, because finding solutions often meant peeking behind the curtain and reading the source files on the [dotnet/aspnetcore](https://github.com/dotnet/aspnetcore) repo.

Because of this however, I've learned an awful lot about how Blazor WebAssembly works; what the limitations and weird quirks are, etc. It also just broadened my knowledge of the .NET ecosystem as a whole.