Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/smapiot/piral.blazor
All .NET things to make Blazor work seamlessly in microfrontends using Piral. :jigsaw:
https://github.com/smapiot/piral.blazor
aspnet-core blazor hacktoberfest microfrontends piral plugin spa webassembly
Last synced: 7 days ago
JSON representation
All .NET things to make Blazor work seamlessly in microfrontends using Piral. :jigsaw:
- Host: GitHub
- URL: https://github.com/smapiot/piral.blazor
- Owner: smapiot
- License: mit
- Created: 2020-04-14T12:46:13.000Z (over 4 years ago)
- Default Branch: blazor-8.0
- Last Pushed: 2024-12-04T15:14:10.000Z (about 1 month ago)
- Last Synced: 2025-01-01T07:02:13.479Z (7 days ago)
- Topics: aspnet-core, blazor, hacktoberfest, microfrontends, piral, plugin, spa, webassembly
- Language: C#
- Homepage: https://piral.io
- Size: 1.79 MB
- Stars: 55
- Watchers: 9
- Forks: 17
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
[![Piral Logo](https://github.com/smapiot/piral/raw/main/docs/assets/logo.png)](https://piral.io)
# Piral.Blazor · [![GitHub License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/smapiot/piral.blazor/blob/blazor-8.0/LICENSE) [![Build Status](https://smapiot.visualstudio.com/piral-pipelines/_apis/build/status/smapiot.piral.blazor?branchName=blazor-8.0)](https://smapiot.visualstudio.com/piral-pipelines/_build/latest?definitionId=48&branchName=blazor-8.0) [![GitHub Tag](https://img.shields.io/github/tag/smapiot/Piral.Blazor.svg)](https://github.com/smapiot/Piral.Blazor/releases) [![GitHub Issues](https://img.shields.io/github/issues/smapiot/Piral.Blazor.svg)](https://github.com/smapiot/Piral.Blazor/issues) [![Gitter Chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/piral-io/blazor) [![Feed Status](https://img.shields.io/uptimerobot/status/m783654792-cfe3913c7481e0f44c143f63)](https://status.piral.io/)
All .NET things to make Blazor work seamlessly in microfrontends using
Piral.> This is the branch for Blazor 8.0 with .NET 8.0. If you want to switch to Blazor with the older .NET Core 3.2, please refer to the [`blazor-3.2`](https://github.com/smapiot/Piral.Blazor/tree/blazor-3.2), [`blazor-5.0`](https://github.com/smapiot/Piral.Blazor/tree/blazor-5.0), [`blazor-6.0`](https://github.com/smapiot/Piral.Blazor/tree/blazor-6.0), or[`blazor-7.0`](https://github.com/smapiot/Piral.Blazor/tree/blazor-7.0) branch. For the most recent version see the [`blazor-9.0`](https://github.com/smapiot/Piral.Blazor/tree/blazor-9.0) branch.
## Getting Started
> You'll also find some information in the [piral-blazor](https://www.npmjs.com/package/piral-blazor) package.
### Creating a Blazor Pilet
We recommend that you watch the video [on scaffolding from the standard VS template](https://youtu.be/Ychzp2xMxes) before you go over the details below.
In general, to create a Blazor pilet using `Piral.Blazor`, two approaches can be used:
#### 1. From Scratch
In this case, it is highly recommended to use our template. More information and installation instructions can be found in [`Piral.Blazor.Template`](https://www.nuget.org/packages/Piral.Blazor.Template).
#### 2. Transforming an Existing Application
In this case, follow these steps:
1. Add a `PiralInstance` property to your `.csproj` file (The Piral instance name should be the name of the Piral instance you want to use, as it is published on npm.)
```xml
net8.0
my-piral-instance
```(You can optionally also specify an `NpmRegistry` property. The default for this is set to `https://registry.npmjs.org/`)
2. Install the `Piral.Blazor.Tools` and `Piral.Blazor.Utils` packages, make sure they both have a version number of format `8.0.x`
3. Remove the `Microsoft.AspNetCore.Components.WebAssembly.DevServer` package and install the `Piral.Blazor.DevServer` package (using the same version as the packages from (2))
4. Rename `Program.cs` to `Module.cs`, and make sure to make the `Main` method an empty method.
5. Build the project. The first time you do this, this can take some time as it will fully scaffold the pilet.If you run the solution using `F5` the `Piral.Blazor.DevServer` will start the Piral CLI under the hood. This allows you to not only use .NET Hot-Reload, but also replace the pilets on demand.
## Usage
### Build Configuration
The `*.csproj` file of your pilet offers you some configuration steps to actually tailor the build to your needs.
Here is a minimal example configuration:
```xml
net8.0
../../app-shell/dist/emulator/app-shell-1.0.0.tgz
```
This one gets the app shell from a local directory. Realistically, you'd have your app shell on a registry. In case of the default registry it could look like
```xml
net8.0
@mycompany/app-shell
```
but realistically you'd publish the app shell to a private registry on a different URL. In such scenarios you'd also make use of the `NpmRegistry` setting:
```xml
net8.0
@mycompany/app-shell
https://registry.mycompany.com/
```
Besides these two options (required `PiralInstance` and optional `NpmRegistry`) the following settings exist:
- `Version`: Sets the version of the pilet. This is a/the standard project property.
- `PiralInstance`: Sets the name (or local path) of the app shell.
- `NpmRegistry`: Sets the URL of the npm registry to use. Will be used for getting npm dependencies of the app shell (and the app shell itself).
- `Bundler`: Sets the name of the bundler to use. By default this is `esbuild`. The list of all available bundlers can be found [in the Piral documentation](https://docs.piral.io/reference/documentation/bundlers).
- `ProjectsWithStaticFiles`: Sets the names of the projects that contain static files, which require to be copied to the output directory. Separate the names of these projects by semicolons.
- `Monorepo`: Sets the behavior of the scaffolding to a monorepo mode. The value must be `enable` to switch this on.
- `PiralCliVersion`: Determines the version of the `piral-cli` tooling to use. By default this is `latest`.
- `PiralBundlerVersion`: Determines the version of the `piral-cli-` to use. By default, this is the same as the value of the `PiralCliVersion`.
- `OutputFolder`: Sets the temporary output folder for the generated pilet (default: `..\piral~`).
- `ConfigFolder`: Sets the folder where the config files are stored (default: *empty*, i.e., current project folder).
- `MocksFolder`: Sets the folder where the Kras mock files are stored (default: `.\mocks`).
- `PiletKind`: Sets the pilet kind (values: `global`, `local`; default: `local`). Global pilets will always be published without trimming.
- `PiletPriority`: Sets the optional priority of the pilet when loading (any representable positive number). DLLs of Blazor pilets with higher numbers will *always* be loaded before the current DLLs (default: *none*).
- `PublishFeedUrl`: Sets the URL to be used for publishing the pilet. If this is left free then using "Publish" in Visual Studio will not trigger a publish of the pilet.
- `PublishFeedApiKey`: Sets the API Key to be used when publishing the pilet. If this is left free then the interactive upload is used, which will open a web browser for logging into the feed service.A more extensive example:
```xml
net8.0
1.2.3
@mycompany/app-shell
next
1.1.0
https://registry.mycompany.com/
esbuild
disable
designsystem;
someotherproject;
thirdproj
999
```
While pilets that define `PiletKind` to be `global` only have *shared dependencies*, the default for `local` pilets is to have *integrated dependencies*. If certain dependencies of `local` pilets should also be loaded into the global context (effectively sharing the dependency between all pilets - independent of the version) then you need to put those dependencies into a dedicated `ItemGroup` using the `Label` `shared`:
```xml
```
### Pages
A standard page in Blazor, using the `@page` directive, will work as expected, and will be automatically registered on the pilet API.
### Extensions
To register an extension, the `PiralExtension` attribute can be used. You will also have to provide the extension slot name that defines where the extension should be rendered. The component can even be registered into multiple slots using multiple attributes.
```razor
//counter.razor@attribute [PiralExtension("my-counter-slot")]
@attribute [PiralExtension("another-extension-slot")]Counter
Current count: @currentCount
Click me
@code {
int currentCount = 0;void IncrementCount()
{
currentCount++;
}
}
```To use an extension within a Blazor component, the `` component can be used.
```razor
```
### Components, Tiles, Menu Items, and Others
To register a Blazor component for use in the pilet API, the `PiralComponent` attribute can be used in two ways:
1. `[PiralComponent]`, this will register the component using the fully qualified name.
2. `[PiralComponent()]` will register the component using the custom name provided.To register these components onto the pilet API, a `setup.tsx` file should be created at the root of your Blazor project.
This file may then, for example to register a tile, look like this:
```tsx
import { PiletApi } from '../piral~//node_modules/';type AddScript = (path: string, attrs?: Record) => void;
type AddStyles = (path: string, pos?: 'first' | 'last' | 'before' | ' after') => void;export default (app: PiletApi, addScript: AddScript, addStyles: AddStyles) => {
//for a component marked with[PiralComponent("my-tile")]
app.registerTile(app.fromBlazor('my-tile'));
};
```The `addScript` function can be used to actually add more scripts, e.g.:
```tsx
export default (app: PiletApi, addScript: AddScript, addStyles: AddStyles) => {
addScript("_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js");
};
```The first argument is the (relative) path to the RCL script, while the optional second argument provides additional attributes for the script to be added to the DOM.
The `addStyles` function can be used to add more style sheets, e.g.:
```tsx
export default (app: PiletApi, addScript: AddScript, addStyles: AddStyles) => {
addStyles("_content/MudBlazor/MudBlazor.min.css");
};
```**Important**: Non-abstract / exposed components with `PiralComponent` cannot have a type parameter. As these are directly instantiated from JavaScript there is no way to define the type to be used. As such, you cannot mark components as `@[PiralComponent]` and `@typeparam`. If you want to use a generic component, then wrap it (i.e., use a second component declared as a `PiralComponent`, which only mounts / renders the first component with the desired generic type).
### Using Parameters
Parameters (or "props") are properly forwarded. Usually, it should be sufficient to declare `[Parameter]` properties in the Blazor components. Besides, there are more advanced ways.
#### Generic Approach
For instance, to access the `params` prop of an extension you can use the `PiralParameter` attribute. This way, you can "forward" props from JS to the .NET name of your choice (in this case "params" is renamed to "Parameters").
```razor
@attribute [PiralExtension("sample-extension")]@Parameters.Test@code {
public class MyParams
{
public string Test { get; set; }
}[Parameter]
[PiralParameter("params")]
public MyParams Parameters { get; set; }
}
```For the serialization you'll need to use either a `JsonElement` or something that can be serialized into. In this case, we used a class called `MyParams`.
**Important**: Make sure that your classes here are *serializable*, i.e., that they have a default / empty constructor (no parameters) and are public. Best case: These should be [POCOs](https://en.wikipedia.org/wiki/Plain_old_CLR_object).
With the `PiralParameter` you can also access / forward children to improve object access:
```razor
@attribute [PiralExtension("sample-extension")]@Message@code {
[Parameter]
[PiralParameter("params.Test")]
public string Message { get; set; }
}
```That way, we only have a property `Message` which reflects the `params.Test`. So if the extension is called like that:
```jsx
```
It would just work.
#### Routes
If you want to match the route parameter you can use the generic approach, too:
```razor
@page "/foo/{id}"@Id@code {
[Parameter]
[PiralParameter("match.params.id")]
public string Id { get; set; }
}
```However, since using `match.params` is quite verbose and easy to get wrong you can also use the special `PiralRouteParameter` attribute.
```razor
@page "/foo/{id}"@Id@code {
[Parameter]
[PiralRouteParameter("id")]
public string Id { get; set; }
}
```Note that there is another convenience deriving from the use of `PiralRouteParameter`. If your route parameter name matches the name of the property then you can also omit the argument:
```razor
@page "/foo/{Id}"@Id@code {
[Parameter]
[PiralRouteParameter]
public string Id { get; set; }
}
```In addition to route parameters you can also match the query parameters using the `PiralQueryParameter` attribute:
```cs
@page "/foo"@Id@code {
[Parameter]
[PiralQueryParameter]
public string Id { get; set; }
}
```The previous example would match `/foo?id=bar` with `Id` being set to `bar`. You could also change the name of the used query parameter:
```cs
@page "/foo"@SearchQuery@code {
[Parameter]
[PiralQueryParameter("q")]
public string SearchQuery { get; set; }
}
```This would print `hello` for `/foo?q=hello`.
### Dependency Injection
You can define services for dependency injection in a `Module` class. The name of the class is arbitrary, but it shows the difference to the standard `Program` class, which should not be available, as mentioned before.
To be able to compile successfully, a `Main` method should be declared, which should remain empty.
```cs
public class Module
{
public static void Main()
{
// this entrypoint should remain empty
}public static void ConfigureServices(IServiceCollection services)
{
// configure dependency injection for the components in the pilet here
}
}
```The `ConfigureServices` method is optional. If you want to configure dependency injection in your pilet then use this.
If a third-party library requires globally shared dependencies (or global injected DI) then add it to a global pilet (setting the `PiletKind` to `global` in the csproj / build configuration).
**Important**: Global pilets will rewrite scoped services to singleton services. By default, Blazor WASM will operate in a singleton context, which means that the basic idea for scoped services should be restricted to specific use cases that are bound to pilets. As such, global pilets try to support the idea of providing *shared* service-instances by avoiding scoping.
Additionally, the `ConfigureServices` method supports another argument providing the configuration of the pilet, i.e., the `IConfiguration` object. So, the example above could be rewritten to be:
```cs
public class Module
{
public static void Main()
{
// this entrypoint should remain empty
}public static void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
}
}
```The configuration uses the `meta.config` of the Pilet API provided by the pilet.
**Important**: There is no support for the *appsettings...json* file as the configuration is assumed to be distributed. Use the `meta.config` approach described below for local development and a proper feed service with configuration support for production purposes.
### Standard Pilet Service
Every pilet gets automatically a service called `IPiletService` injected.
#### Asset URLs
The `IPiletService` service can be used to compute the URL of a resource.
```razor
@inject IPiletService Pilet
```The relevant helper method is `GetUrl`. You can use it like:
```razor
@page "/example"
@inject IPiletService Pilet
```In the example above the resource `images/something.png` would be placed in the `wwwroot` folder (i.e., `wwwroot/images/something`). As the content of the `wwwroot` folder is copied, the image will also be copied. However, the old local URL is not valid in a pilet, which needs to prefix its resources with its base URL. The function above does that. In that case, the URL would maybe be something like `http://localhost:1234/$pilet-api/0/images/something.png` while debugging, and another fully qualified URL later in production.
#### Events
You can use the `IPiletService` service to emit and receive events via the standard Pilet API event bus. This is great for doing loosely-coupled pilet-to-pilet communication.
Example:
```razor
@attribute [PiralComponent]
@inject IPiletService ps
@implements IDisposable@code {
[Parameter]
public bool IsOpen { get; set; } = false;[Parameter]
public EventCallback IsOpenChanged { get; set; }string _sidebarClass { get => IsOpen ? "sidebar open" : "sidebar"; }
public void Dispose()
{
ps.RemoveEventListener("toggle-sidebar", ToggleSidebar);
}protected override void OnInitialized()
{
ps.AddEventListener("toggle-sidebar", ToggleSidebar);
}public void ToggleSidebar(bool value) => IsOpenChanged.InvokeAsync(value);
public void CloseSidebar() => ToggleSidebar(false);
}
```Another component can now trigger this by using `ps.DispatchEvent("toggle-sidebar", false);` with an injected `@inject IPiletService ps`.
#### Pilet Data and API Access
You can use the `IPiletService` service to call methods living on the pilet API. This makes mostly sense for APIs that are quite primitive, e.g., accepting and returning only strings, booleans, and integers.
In general this is working via the `Call` API. An example would be:
```razor
@attribute [PiralComponent]
@inject IPiletService psLog current value
@code {
public async void LogValue()
{
var value = await ps.Call("getData", "myValue");
Console.WriteLine("Currently stored value is: {0}", value);
}
}
```For some more common pilet API functions extension methods exist. The call beforehand to the `getData` function could be simplified with the `GetDataValue` extension:
```razor
@attribute [PiralComponent]
@inject IPiletService psLog current value
@code {
public async void LogValue()
{
var value = await ps.GetDataValue("myValue");
Console.WriteLine("Currently stored value is: {0}", value);
}
}
```### Localization
Localization works almost exactly as with standard Blazor, except that the language can be changed at runtime directly rather then requiring a full reload of the page.
The other difference is that the initial language is no longer decided by the server's response headers, but rather by the app shell. The initial configuration options of the `piral-blazor` plugin allow setting the `initialLanguage`. These options also allow setting up a callback to decide when to change the language (and to what language). If not explicitly stated Blazor will just listen to the `select-language` event of Piral, providing a key `currentLanguage` in the event arguments.
To dynamically change / refresh your components when the language change you'll need to listen to the `LanguageChanged` event emitted by the injected `IPiletService` instance:
```razor
@inject IStringLocalizer loc
@inject IPiletService pilet@loc["greeting"]
@code {
protected override void OnInitialized()
{
pilet.LanguageChanged += (s, e) => this.StateHasChanged();
base.OnInitialized();
}
}
```This way, your components will always remain up-to-date and render the right translations.
### Provider Components
Sometimes Blazor components require some global components (or "providers") to be added. To accomplish this you can create components marked with the `PiralProviderAttribute` attribute.
Example:
```razor
@attribute [PiralProvider]```
Providers will *never* receive any parameters - they are rendered only once and will remain active for the whole lifecycle of the application. There can be more than one provider.
Provider components are adjacent to your other components, which may come and go and will be - in general - somewhere else in the DOM. As such they are not ideal for providing some cascading value or other properties. They are ideal, however, when you need something running all the time.
In contrast, Piral also has the concept of a root component, which comes with another set of constraints.
### Root Component
By default, the Blazor pilets run in a dedicated Blazor application with no root component. If you need a root component, e.g., to provide some common values from a `CascadingValue` component such as `CascadingAuthenticationState` from the `Microsoft.AspNetCore.Components.Authorization` package, you can actually override the default root component:
```razor
@attribute [PiralAppRoot]@ChildContent
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
}
```You can also provide your own providers here (or nest them as you want):
```razor
@attribute [PiralAppRoot]
@ChildContent
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
private string theme = "dark";
}
```**Note**: There is always just one `PiralAppRoot` component. If you did not supply one then the default `PiralAppRoot` will be used. If you already provided one, no other `PiralAppRoot` can be used.
It is critical to understand that each attached pilet component starts its own Blazor rendering tree. Therefore, while there is just a single `PiralAppRoot` component there might be multiple instances active at a given point in time. This is a crucial difference to `PiralProvider` components, which are essentially singletons from a rendering perspective.
### HTTP Interceptors
Pilets can add *global* HTTP interceptors, which will be triggered for all HTTP requests using the *global* `HttpClient`. This can be done by adding a singleton `IHttpInterceptor` to the services, e.g.:
```cs
public class Module
{
public static void Main() {}public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton();
}
}
```The interceptor itself has methods to react to a request (before sending the actual request) or response (after receiving the response).
```cs
class MyInterceptor : IHttpInterceptor
{
public Task OnRequest(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Add some header
request.Headers.Add("x-foo-bar", "other");
return Task.FromResult(request);
}public Task OnResponse(HttpResponseMessage response, CancellationToken cancellationToken)
{
// Don't do anything
return Task.FromResult(response);
}
}
```In case you want to intercept calls for injecting a bearer token obtained from calling the `getAccessCode()` pilet API you can just add a simple convenience service:
```cs
public class Module
{
public static void Main() {}public static void ConfigureServices(IServiceCollection services)
{
services.AddAccessCodeInterceptor();
}
}
```This way the current access code is retrieved and inserted into the request via the `Authorization` header.
## Running and Debugging the Pilet :rocket:
From your Blazor project folder, run your pilet via the Piral CLI:
```sh
cd ../piral~/
npm start
```In addition to this, if you want to debug your Blazor pilet using for example Visual Studio, these requirements should be considered:
- keep the Piral CLI running
- debug your Blazor pilet using IISExpress> :warning: if you want to run your pilet and directly visit it in the browser without debugging via IISExpress, you will have to disable a [kras](https://github.com/FlorianRappl/kras) script injector **before** visiting the pilet. To do this, go to `http://localhost:1234/manage-mock-server/#/injectors`, disable the `debug.js` script, and save your changes. Afterwards, you can visit `http://localhost:1234`.
## Special Files
There are some special files that you can add in your project (adjacent to the *.csproj* file):
- *setup.tsx*
- *teardown.tsx*
- *package-overwrites.json*
- *meta-overwrites.json*
- *kras-overwrites.json*
- *js-imports.json***Note**: The location of these files can also be changed through the `ConfigFolder` option. By default, this one is empty, i.e., all files have to be placed adjacent to the *.csproj* file as mentioned above. However, if you set the value to, e.g., *.piletconfig* then the files will be retrieved from this subdirectory. For instance, the setup file would then be read from *.piletconfig/setup.tsx*.
Let's see what these files do and how they can be used.
### Extending the Pilet's Setup
The *setup.tsx* file can be used to define more things that should be done in a pilet's `setup` function. By default, the content of the `setup` function is auto generated. Things such as `@page /path-to-use` components or components with `@attribute [PiralExtension("name-of-slot")]` would be automatically registered. However, already in case of `@attribute [PiralComponent]` we have a problem. What should this component do? Where is it used?
The solution is to use the *setup.tsx* file. An example:
```js
export default (app) => {
app.registerMenu(app.fromBlazor('counter-menu'));app.registerExtension("ListToggle", app.fromBlazor('counter-preview'));
};
```This example registers a pilet's component named "counter-menu" as a menu entry. Furthermore, it also adds the "counter-preview" component as an extension to the "ListToggle" slot.
Anything that is available on the Pilet API provided via the `app` argument is available in the function. The only import part of *setup.tsx* is that has a default export - which is actually a function.
### Overwriting the Package Manifest
The generated / used pilet is a standard npm package. Therefore, it will have a *package.json*. The content of this *package.json* is mostly pre-determined. Things such as `piral-cli` or the pilet's app shell package are in there. In some cases, additional JS dependencies for runtime or development aspects are necessary or useful. In such cases the *package-overwrites.json* comes in handy.
For instance, to actually extend the `devDependencies` you could write:
```json
{
"devDependencies": {
"axios": "^0.20.0"
}
}
```This would add a development dependency to the `axios` package. Likewise, other details, such as a publish config or a description could also be added / overwritten:
```json
{
"description": "This is my pilet description.",
"publishConfig": {
"access": "public"
}
}
```The rules for the merge follow the [Json.NET](https://www.newtonsoft.com/json/help/html/MergeJson.htm) approach.
### Overwriting the Debug Meta Data
The generated / used pilet is served from the local file system instead of a feed service. Therefore, it will not have things like a configuration store. However, you might want to use one - or at least test against one. For this, usually a *meta.json* file can be used. The content of this *meta.json* is then merged into the metadata of a served pilet. For Piral.Blazor this file is premade, however, its content can still be overwritten using a *meta-overwrites.json* file.
For instance, to include a custom `config` field (with one config called `backendUrl`) in the pilet's metadata you can use the following content:
```json
{
"config": {
"backendUrl": "http://localhost:7345"
}
}
```The rules for the merge follow the [Json.NET](https://www.newtonsoft.com/json/help/html/MergeJson.htm) approach.
### Extending the Pilet's Teardown
The *teardown.tsx* file can be used to define more things that should be done in a pilet's `teardown` function. By default, the content of the `teardown` function is auto generated. Things such as `pages` and `extensions` would be automatically unregistered. However, in some cases you will need to unregister things manually. You can do this here.
### Defining Additional JavaScript Imports
Some Blazor dependencies require additional JavaScript packages in order to work correctly. The *js-imports.json* file can be to declare all these. The files will then be added via a generated `import` statement in the pilet's root module.
The content of the *js-imports.json* file is a JSON array. For example:
```json
[
"axios",
"global-date-functions"
]
```Includes the two dependencies via the respective `import` statements.
### DevServer Settings
The `Piral.Blazor.DevServer` can be configured, too. Much like the standard / official Blazor DevServer you can introduce a *blazor-devserversettings.json* file that describes more options. Most of the contained options are the same as the one for the official Blazor DevServer.
Current options found in the `Piral` section:
- `forwardedPaths` - is an array of strings describing the path segments that should be forwarded to the Piral CLI dev server (using kras)
Example:
```json
{
"Piral": {
"forwardedPaths": [ "/foo" ]
}
}
```- `feedUrl` - is a string defining an URL for including an external / remote feed of pilets into the debug process
Example:
```json
{
"Piral": {
"feedUrl": "https://feed.piral.cloud/api/v1/pilet/sample"
}
}
```In addition, the options for the DevServer also touch the configured options for the `Piral.Blazor.Tools`, such as `OutputFolder` which is used to define where the scaffolded pilet is stored.
### Setting the Logging Level
The log level can be set either within your Blazor pilets using the `ILoggingConfiguration` service or from JavaScript:
```js
DotNet.invokeMethodAsync('Piral.Blazor.Core', 'SetLogLevel', logLevel);
```Here, the value for `logLevel` should be between 0-6, where 0 logs everything (even traces) and 6 logs nothing. Alternatively, you can also set a log level when initializing `piral-blazor`.
## FAQ
1. I cannot use breakpoints when I debug a Piral.Blazor pilet in VS. What could be wrong?
Make sure you actually emit a PDB and have `Debug` selected as configuration. Also, don't change the configuration to have `Full` or similar in the project file. You'll need a portable PDB (modern format), not a full PDB (legacy format for Windows).
## License
Piral.Blazor is released using the MIT license. For more information see the [license file](https://raw.githubusercontent.com/smapiot/Piral.Blazor/blazor-6.0/LICENSE).