Skip to content

Commit

Permalink
Blazor custom elements package (#377)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSandersonMS committed Aug 2, 2021
1 parent de99be4 commit bd5cd1f
Show file tree
Hide file tree
Showing 47 changed files with 25,121 additions and 2 deletions.
1 change: 1 addition & 0 deletions NuGet.config
Expand Up @@ -2,6 +2,7 @@
<configuration>
<packageSources>
<clear />
<add key="dotnet6" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json" />
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" />
Expand Down
1 change: 1 addition & 0 deletions eng/Build.props
Expand Up @@ -10,5 +10,6 @@
<ProjectToBuild Include="$(MSBuildThisFileDirectory)..\src\WebHooks\WebHooks.sln" />
<ProjectToBuild Include="$(MSBuildThisFileDirectory)..\src\Components.Web.Extensions\Microsoft.AspNetCore.Components.Web.Extensions.csproj" />
<ProjectToBuild Include="$(MSBuildThisFileDirectory)..\src\ApiTemplate\ApiTemplatePackage.csproj" />
<ProjectToBuild Include="$(MSBuildThisFileDirectory)..\src\BlazorCustomElements\BlazorCustomElements.sln" />
</ItemGroup>
</Project>
4 changes: 2 additions & 2 deletions global.json
@@ -1,9 +1,9 @@
{
"sdk": {
"version": "6.0.100-preview.7.21364.4"
"version": "6.0.100-rc.1.21401.2"
},
"tools": {
"dotnet": "6.0.100-preview.7.21364.4",
"dotnet": "6.0.100-rc.1.21401.2",
"runtimes": {
"aspnetcore/x64": [
"3.1.14"
Expand Down
27 changes: 27 additions & 0 deletions src/BlazorCustomElements/BlazorCustomElements.sln
@@ -0,0 +1,27 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{009CDD48-853D-48F0-A9CC-008C0F106F63}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.CustomElements", "src\Microsoft.AspNetCore.Components.CustomElements\Microsoft.AspNetCore.Components.CustomElements.csproj", "{98718095-4FE7-4698-9AED-B16B0E1AF59D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{98718095-4FE7-4698-9AED-B16B0E1AF59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98718095-4FE7-4698-9AED-B16B0E1AF59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{98718095-4FE7-4698-9AED-B16B0E1AF59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98718095-4FE7-4698-9AED-B16B0E1AF59D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{98718095-4FE7-4698-9AED-B16B0E1AF59D} = {009CDD48-853D-48F0-A9CC-008C0F106F63}
EndGlobalSection
EndGlobal
163 changes: 163 additions & 0 deletions src/BlazorCustomElements/README.md
@@ -0,0 +1,163 @@
# Blazor Custom Elements

This package provides a simple mechanism for using Blazor components as custom elements. This means you can easily render Blazor components dynamically from other SPA frameworks, such as Angular or React.

## Running the Angular sample

Clone this repo. Then, in a command prompt, execute:

* `cd samples/BlazorAppProvidingCustomElements`
* `dotnet watch`

Leave that running, and open a second command prompt, and execute:

* `cd samples/angular-app-with-blazor`
* `npm install`
* `npm start`

Now when you browse to http://localhost:4200/, you'll see an Angular application that dynamically renders Blazor WebAssembly components, passing parameters to them.

## Running the React sample

Clone this repo. Then, in a command prompt, execute:

* `cd samples/BlazorAppProvidingCustomElements`
* `dotnet watch`

Leave that running, and open a second command prompt, and execute:

* `cd samples/react-app-with-blazor`
* `yarn install`
* `yarn start`

Now when you browse to http://localhost:3000/, you'll see a React application that dynamically renders Blazor WebAssembly components, passing parameters to them.

## Adding this to your own project

1. Start with or create a Blazor WebAssembly or Blazor Server project that contains your Blazor components
2. Install the NuGet package `Microsoft.AspNetCore.Components.CustomElements`
3. In your Blazor application startup configuration, remove any normal Blazor root components, and instead register components as custom elements. See [Example for Blazor WebAssembly](#webassembly-example) or [Example for Blazor Server](#server-example).
4. Configure your external SPA framework application to serve the Blazor framework files and render your Blazor custom elements. For example, see [Configuring Angular](#configuring-angular) or [Configuring React](#configuring-react). Similar techniques will work with other SPA frameworks.

### WebAssembly example

For Blazor WebAssembly, remove lines like this from `Program.cs`:

```cs
builder.RootComponents.Add<App>("#root");
```

... and add lines like the following instead:

```cs
builder.RootComponents.RegisterAsCustomElement<Counter>("my-blazor-counter");
builder.RootComponents.RegisterAsCustomElement<ProductGrid>("product-grid");
```

### Server example

For Blazor Server, in `Program.cs` or `Startup.cs`, change the call to `AddServerSideBlazor` to pass an `options` callback like the following:

```cs
builder.Services.AddServerSideBlazor(options =>
{
options.RootComponents.RegisterAsCustomElement<Counter>("my-blazor-counter");
options.RootComponents.RegisterAsCustomElement<ProductGrid>("product-grid");
});
```

### Configuring Angular

1. In your Angular `index.html`, add the following at the end of `<body>`:

```html
<script src="_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
```

Note that the tag for `BlazorCustomElements.js` is only needed temporarily. This step will be removed in a future update.

2. Run the application now and see that the browser gets a 404 error for those two JavaScript files. To fix this, [configure Angular CLI to proxy unmatches requests to the ASP.NET Core development server](https://angular.io/guide/build#proxying-to-a-backend-server). Of course, you also need to be running your ASP.NET Core development server at the same time for this to work.

3. In any of your Angular components, render the custom elements corresponding to your Blazor components. For example, add to a `template` markup like the following:

```html
<my-blazor-counter [attr.title]="counter.title"></my-blazor-counter>
```

This assumes that your component declares a parameter like the following:

```cs
[Parameter] public string Title { get; set; }
```

If you have parameters that are complex-typed objects, you can use Angular's property-setting syntax as follows:

```html
<my-blazor-counter [someComplexObject]="someJsObject"></my-blazor-counter>
```

This will cause `someJsObject` to be JSON-serialized and supplied as a Blazor component parameter called `SomeComplexObject`.

4. If you get a compiler error saying that your custom element is "not a known element", [add `CUSTOM_ELEMENTS_SCHEMA` to your Angular component or module](https://stackoverflow.com/a/40407697).

### Configuring React

1. In your React `index.html`, add the following at the end of `<body>`:

```html
<script src="_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
```

Note that the tag for `BlazorCustomElements.js` is only needed temporarily. This step will be removed in a future update.

2. Run the application now and see that the browser gets a 404 error for those two JavaScript files. To fix this, [configure React's development server to proxy unmatches requests to the ASP.NET Core development server](https://create-react-app.dev/docs/proxying-api-requests-in-development/). Of course, you also need to be running your ASP.NET Core development server at the same time for this to work.

3. In any of your React components, render the custom elements corresponding to your Blazor components. For example, have a React component render output include:

```html
<my-blazor-counter title={title} increment-amount={incrementAmount}></my-blazor-counter>
```

Whenever you edit your React component source code, you'll see it update automatically in the browser without losing the state within your Blazor component. The two hot reload systems can coexist and cooperate.

### Publishing your combined application

The easiest way to have an application with both .NET parts and a 3rd-party SPA framework is to use the ASP.NET Core project templates [for Angular](https://docs.microsoft.com/aspnet/core/client-side/spa/angular) or [for React](https://docs.microsoft.com/aspnet/core/client-side/spa/react). This will automatically gather the files required for the combined application during publishing.

If you're not using one of those project templates, you'll need to publish both the .NET and JavaScript parts separately and manually combine the files into a single deployable set.

## Passing parameters

You can pass parameters to your Blazor component either as HTML attributes or as JavaScript properties on the DOM element.

For example, if your component declares a parameters like the following:

```cs
[Parameter] public int IncrementAmount { get; set; }
```

... then you can pass a value as an HTML attribute follows:

```html
<my-blazor-counter increment-amount="123"></my-blazor-counter>
```

Notice that the attribute name is kebab-case (i.e., `increment-amount`, not `IncrementAmount`).

Alternatively, you can set it as a JavaScript property on the element object:

```js
const elem = document.querySelector("my-blazor-counter");
elem.incrementAmount = 123;
```

Notice that the property name is camelCase (i.e., `incrementAmount`, not `IncrementAmount`).

You can update parameter values at any time using either attribute or property syntax.

### Supported parameter types

* Using JavaScript property syntax, you can pass objects of any JSON-serializable type
* Using HTML attributes, you are limited to passing objects of string, boolean, or numerical types
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0-rc.1.21381.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0-rc.1.21381.3" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Components.CustomElements\Microsoft.AspNetCore.Components.CustomElements.csproj" />
</ItemGroup>

</Project>
@@ -0,0 +1,18 @@
<h1>@Title</h1>

<p role="status">Current count: @currentCount</p>
<p>Increment amount: @IncrementAmount</p>

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

@code {
private int currentCount = 0;

[Parameter] public string Title { get; set; } = "Blazor Counter";
[Parameter] public int? IncrementAmount { get; set; }

private void IncrementCount()
{
currentCount += IncrementAmount.GetValueOrDefault(1);
}
}
@@ -0,0 +1,17 @@
button {
font-weight: bold;
background-color: #7b31b8;
color: white;
border-radius: 4px;
padding: 4px 12px;
border: none;
}

button:hover {
background-color: #9654cc;
cursor: pointer;
}

button:active {
background-color: #b174e4;
}
@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using BlazorAppProvidingCustomElements;

var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.RootComponents.RegisterAsCustomElement<Counter>("my-blazor-counter");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

await builder.Build().RunAsync();
@@ -0,0 +1,9 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using BlazorAppProvidingCustomElements
@@ -0,0 +1,7 @@
<html>
<body>
<h1>Don't visit this page directly.</h1>
<p>This project exists to provide custom elements to other projects.</p>
<p>See README.md for instructions on running the custom element samples.</p>
</body>
</html>
@@ -0,0 +1,17 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries

# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support

# You can see what browsers were selected by your queries by running:
# npx browserslist

last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
@@ -0,0 +1,45 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out

# dependencies
/node_modules

# profiling files
chrome-profiler-events*.json

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings

# System Files
.DS_Store
Thumbs.db

0 comments on commit bd5cd1f

Please sign in to comment.