Skip to content

Commit

Permalink
feat: Add support for static assets embedded in another ASP.NET app
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Oct 5, 2022
1 parent a436679 commit c9bb187
Show file tree
Hide file tree
Showing 33 changed files with 691 additions and 46 deletions.
26 changes: 26 additions & 0 deletions .vsts-ci-linux.yml
Expand Up @@ -139,3 +139,29 @@ jobs:
PathtoPublish: $(build.artifactstagingdirectory)
ArtifactName: uno-wasm-bootstrap-linux-aot
ArtifactType: Container

## Static linking server validation (net7)
- bash: |
$(build.sourcesdirectory)/build/scripts/run-tests-server.sh \
"$(build.sourcesdirectory)/src/Uno.Wasm.StaticLinking.Server.net7" \
"$(build.sourcesdirectory)/src/Uno.Wasm.StaticLinking.Aot.UITests" "http://localhost:8000/"
displayName: StaticLinking.Aot Server Tests net7
env:
BUILD_SOURCESDIRECTORY: "$(build.sourcesdirectory)"
- task: CopyFiles@2
condition: always()
inputs:
SourceFolder: $(build.sourcesdirectory)/src/Uno.Wasm.StaticLinking.Server.net7/bin/Release/net7.0
Contents: '**/*.*'
TargetFolder: $(build.artifactstagingdirectory)/Uno.Wasm.StaticLinking.Server.net7
CleanTargetFolder: false
OverWrite: false
flattenFolders: false

- task: PublishBuildArtifacts@1
condition: always()
inputs:
PathtoPublish: $(build.artifactstagingdirectory)
ArtifactName: uno-wasm-bootstrap-linux-aot
ArtifactType: Container
7 changes: 3 additions & 4 deletions .vsts-ci.yml
Expand Up @@ -46,10 +46,9 @@ jobs:
displayName: UI Tests npm dependencies
- script: |
cd $(build.sourcesdirectory)\src\Uno.Wasm.Bootstrap
dotnet msbuild /r /p:Configuration=Release /p:InformationalVersion=$(GITVERSION.INFORMATIONALVERSION) /p:PackageReleaseNotesFile=$(Build.SourcesDirectory)/build/CHANGELOG.md /p:PackageVersion=$(GITVERSION.FullSemVer) /p:PackageOutputPath=$(build.sourcesdirectory)\build\nuget /bl:$(build.artifactstagingdirectory)/win-bootstrap.binlog
cd $(build.sourcesdirectory)\src\Uno.Wasm.Bootstrap.DevServer
dotnet msbuild /r /p:Configuration=Release /p:InformationalVersion=$(GITVERSION.INFORMATIONALVERSION) /p:PackageReleaseNotesFile=$(Build.SourcesDirectory)/build/CHANGELOG.md /p:PackageVersion=$(GITVERSION.FullSemVer) /p:PackageOutputPath=$(build.sourcesdirectory)\build\nuget /bl:$(build.artifactstagingdirectory)/win-bootstrap-devserver.binlog
cd $(build.sourcesdirectory)\src
dotnet msbuild Uno.Wasm.Bootstrap-paclages-only.slnf /r /p:Configuration=Release /p:InformationalVersion=$(GITVERSION.INFORMATIONALVERSION) /p:PackageReleaseNotesFile=$(Build.SourcesDirectory)/build/CHANGELOG.md /p:PackageVersion=$(GITVERSION.FullSemVer) /p:PackageOutputPath=$(build.sourcesdirectory)\build\nuget /bl:$(build.artifactstagingdirectory)/win-packages.binlog
cp $(Build.SourcesDirectory)/build/CHANGELOG.md $(build.artifactstagingdirectory)
displayName: Build packages
Expand Down
31 changes: 31 additions & 0 deletions build/scripts/run-tests-server.sh
@@ -0,0 +1,31 @@
#!/bin/bash
set -e

cleanup() {
kill %%
}
trap cleanup 0

npm install @azure/static-web-apps-cli@0.8.3
SWA_PATH=`pwd`/node_modules/.bin/swa

export BOOTSTRAP_APP_PATH=$1
export BOOTSTRAP_TEST_RUNNER_PATH=$2
export BOOTSTRAP_TEST_RUNNER_URL=$3

echo "BOOTSTRAP_APP_PATH=$BOOTSTRAP_APP_PATH"
echo "BOOTSTRAP_TEST_RUNNER_PATH=$BOOTSTRAP_TEST_RUNNER_PATH"
echo "BOOTSTRAP_TEST_RUNNER_URL=$BOOTSTRAP_TEST_RUNNER_URL"

cd $BOOTSTRAP_APP_PATH
dotnet build -c Release

# We're not running using the published build, so we need to set
# environment first.
export ASPNETCORE_ENVIRONMENT=development
dotnet run -c Release --no-build --urls=http://localhost:8000/ &
sleep 5

cd $BOOTSTRAP_TEST_RUNNER_PATH
npm install
node app
1 change: 1 addition & 0 deletions build/scripts/run-tests-swa.sh
Expand Up @@ -19,6 +19,7 @@ echo "BOOTSTRAP_TEST_RUNNER_URL=$BOOTSTRAP_TEST_RUNNER_URL"

cd $BOOTSTRAP_APP_PATH
$SWA_PATH start --port 8000 --app-location "$BOOTSTRAP_APP_PATH" &
sleep 5

cd $BOOTSTRAP_TEST_RUNNER_PATH
npm install
Expand Down
2 changes: 1 addition & 1 deletion build/scripts/run-tests-windows.ps1
@@ -1,4 +1,4 @@
dotnet tool install dotnet-serve --version 1.10.112 --tool-path $BUILD_SOURCESDIRECTORY\build\tools
dotnet tool install dotnet-serve --version 1.10.140 --tool-path $BUILD_SOURCESDIRECTORY\build\tools
$env:PATH="$env:PATH;$BUILD_SOURCESDIRECTORY\build\tools"

$BOOTSTRAP_APP_PATH=$args[0]
Expand Down
3 changes: 2 additions & 1 deletion build/scripts/run-tests.sh
Expand Up @@ -13,11 +13,12 @@ export BOOTSTRAP_TEST_RUNNER_URL=$3
# install dotnet serve / Remove as needed
dotnet tool uninstall dotnet-serve -g || true
dotnet tool uninstall dotnet-serve --tool-path $BUILD_SOURCESDIRECTORY/build/tools || true
dotnet tool install dotnet-serve --version 1.10.112 --tool-path $BUILD_SOURCESDIRECTORY/build/tools || true
dotnet tool install dotnet-serve --version 1.10.140 --tool-path $BUILD_SOURCESDIRECTORY/build/tools || true
export PATH="$PATH:$BUILD_SOURCESDIRECTORY/build/tools"

cd $BOOTSTRAP_APP_PATH
dotnet serve -p 8000 -c -b -h "Cross-Origin-Embedder-Policy: require-corp" -h "Cross-Origin-Opener-Policy: same-origin" &
sleep 5
cd $BOOTSTRAP_TEST_RUNNER_PATH
npm install
node app
17 changes: 17 additions & 0 deletions doc/deploy-and-publish.md
Expand Up @@ -6,6 +6,23 @@ The publication of the application must be done in .NET Framework hosting (and n

For deeper integration in the publishing pipeline, the `WasmShellOutputPackagePath` property is defined by the bootstrapper after the `BuildDist` target, which contains the path to the generated `package_XXX` content.

## Integration with ASP.NET Core

ASP.NET Core hosting is supported through the `Uno.Wasm.Bootstrap.Server` package.

In order to host an Uno Platform App, you'll need to the following:
- Create an `ASP.NET Core Web API` project (call it `MyApp.Server`). You may need to disable swagger for the `index.html` to be served properly.
- Add a NuGet reference to `Uno.Wasm.Bootstrap.Server`
- In your `Program.cs` startup, add the following to setup your `WebApplication` instance:
```
using Uno.Wasm.Bootstrap.Server;
...
app.UseUnoFrameworkFiles();
app.MapFallbackToFile("index.html");
```
- Add a project reference to the `Wasm` project
- Build and deploy `MyApp.Server`

## Serve the Wasm app through Windows Linux Subsystem
Using Windows 10, serving the app through a small Web Server is done through WSL.

Expand Down
2 changes: 1 addition & 1 deletion src/Uno.Wasm.AotTests.net5/Uno.Wasm.AotTests.net5.csproj
Expand Up @@ -48,7 +48,7 @@
</VisualStudio>
</ProjectExtensions>

<Target Name="AfterBuildValidation" AfterTargets="Build">
<Target Name="AfterBuildValidation" AfterTargets="BuildDist">
<ItemGroup>
<_AdditionalFile1 Include="pwa-images\android\android-launchericon-144-144.png" />
<_AdditionalFile1 Include="pwa-images\windows10\SplashScreen.scale-100.png" />
Expand Down
5 changes: 3 additions & 2 deletions src/Uno.Wasm.Bootstrap-msbuild-only.slnf
Expand Up @@ -5,6 +5,7 @@
"LongPathTest\\Uno.Wasm.LongPath.csproj",
"Uno.Wasm.Bootstrap.Cli\\Uno.Wasm.Bootstrap.Cli.csproj",
"Uno.Wasm.Bootstrap.DevServer\\Uno.Wasm.Bootstrap.DevServer.csproj",
"Uno.Wasm.Bootstrap.Server\\Uno.Wasm.Bootstrap.Server.csproj",
"Uno.Wasm.Bootstrap.UnitTests\\Uno.Wasm.Bootstrap.UnitTests.csproj",
"Uno.Wasm.Bootstrap\\Uno.Wasm.Bootstrap.csproj",
"Uno.Wasm.MixedModeRoslynSample\\Uno.Wasm.MixedModeRoslynSample.csproj",
Expand All @@ -18,12 +19,12 @@
"Uno.Wasm.Tests.Empty\\Uno.Wasm.Test.Empty.csproj",
"Uno.Wasm.Tests.TypeScript\\Uno.Wasm.Tests.TypeScript.csproj",
"Uno.Wasm.Threading.UITests\\Uno.Wasm.Threading.UITests.njsproj",
"Uno.Wasm.Threads\\Uno.Wasm.Threads.csproj",
"Uno.Wasm.Threads.Aot\\Uno.Wasm.Threads.Aot.csproj",
"Uno.Wasm.Threads\\Uno.Wasm.Threads.csproj",
"Uno.Wasm.TimezoneData\\Uno.Wasm.TimezoneData.csproj",
"Uno.WasmSample WithSpace Aot\\Uno.WasmSample.WithSpace.Aot.csproj",
"Uno.WasmSample WithSpace\\Uno.WasmSample.WithSpace.csproj",
"WasmAot.UITests\\WasmAot.UITests.njsproj"
]
}
}
}
6 changes: 4 additions & 2 deletions src/Uno.Wasm.Bootstrap-netcore-only.slnf
Expand Up @@ -11,6 +11,7 @@
"Uno.Wasm.MixedModeRoslynSample\\Uno.Wasm.MixedModeRoslynSample.csproj",
"Uno.Wasm.MixedModeSample\\Uno.Wasm.MixedModeSample.csproj",
"Uno.Wasm.Node.Sample\\Uno.Wasm.Node.Sample.csproj",
"Uno.Wasm.Sample.Server.Net7\\Uno.Wasm.Sample.Server.Net7.csproj",
"Uno.Wasm.SampleNet5.Aot\\Uno.Wasm.SampleNet5.Aot.csproj",
"Uno.Wasm.SampleNet5\\Uno.Wasm.SampleNet5.csproj",
"Uno.Wasm.SampleNet6.LogProfiler\\Uno.Wasm.SampleNet6.LogProfiler.csproj",
Expand All @@ -22,9 +23,10 @@
"Uno.Wasm.StaticLinking.Interpreter\\Uno.Wasm.StaticLinking.Interpreter.csproj",
"Uno.Wasm.StaticLinking.PgAot.Net5\\Uno.Wasm.StaticLinking.PgAot.Net5.csproj",
"Uno.Wasm.StaticLinking.PgAot.ProfileGen.Net5\\Uno.Wasm.StaticLinking.PgAot.ProfileGen.Net5.csproj",
"Uno.Wasm.StaticLinking.Server.net7\\Uno.Wasm.StaticLinking.Server.net7.csproj",
"Uno.Wasm.StaticLinking.Shared\\Uno.Wasm.StaticLinking.Shared.shproj",
"Uno.Wasm.StaticLinking.net7\\Uno.Wasm.StaticLinking.net7.csproj",
"Uno.Wasm.StaticLinking.Simd.net7\\Uno.Wasm.StaticLinking.Simd.net7.csproj",
"Uno.Wasm.StaticLinking.net7\\Uno.Wasm.StaticLinking.net7.csproj",
"Uno.Wasm.Tests.Electron\\Uno.Wasm.Tests.Electron.csproj",
"Uno.Wasm.Tests.Empty\\Uno.Wasm.Test.Empty.csproj",
"Uno.Wasm.Tests.Shared\\Uno.Wasm.Tests.Shared.shproj",
Expand All @@ -37,4 +39,4 @@
"Uno.WasmSample WithSpace\\Uno.WasmSample.WithSpace.csproj"
]
}
}
}
18 changes: 18 additions & 0 deletions src/Uno.Wasm.Bootstrap-paclages-only.slnf
@@ -0,0 +1,18 @@
{
"solution": {
"path": "Uno.Wasm.Bootstrap.sln",
"projects": [
"Uno.Wasm.AotProfiler\\Uno.Wasm.AotProfiler.csproj",
"Uno.Wasm.Bootstrap.Cli\\Uno.Wasm.Bootstrap.Cli.csproj",
"Uno.Wasm.Bootstrap.DevServer\\Uno.Wasm.Bootstrap.DevServer.csproj",
"Uno.Wasm.Bootstrap.Server\\Uno.Wasm.Bootstrap.Server.csproj",
"Uno.Wasm.Bootstrap.UnitTests\\Uno.Wasm.Bootstrap.UnitTests.csproj",
"Uno.Wasm.Bootstrap\\Uno.Wasm.Bootstrap.csproj",
"Uno.Wasm.LogProfiler\\Uno.Wasm.LogProfiler.csproj",
"Uno.Wasm.MetadataUpdater\\Uno.Wasm.MetadataUpdater.csproj",
"Uno.Wasm.Packager\\Uno.Wasm.Packager.csproj",
"Uno.Wasm.TimezoneData\\Uno.Wasm.TimezoneData.csproj",
"Uno.Wasm.Tuner\\Uno.Wasm.Tuner.csproj"
]
}
}
138 changes: 138 additions & 0 deletions src/Uno.Wasm.Bootstrap.Server/AspNetExtensions.cs
@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Mime;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Net.Http.Headers;

namespace Uno.Wasm.Bootstrap.Server
{
/// <summary>
/// Extensions for mapping Uno Platform WebAssembly applications.
/// </summary>
public static class AspNetExtensions
{
/// <summary>
/// Configures the application to serve Uno Platform WebAssembly framework files from the path <paramref name="pathPrefix"/>. This path must correspond to a referenced Uno Platform WebAssembly application project.
/// </summary>
/// <param name="builder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="pathPrefix">The <see cref="PathString"/> that indicates the prefix for the Uno Platform WebAssembly application.</param>
/// <returns>The <see cref="IApplicationBuilder"/></returns>
public static IApplicationBuilder UseUnoFrameworkFiles(this IApplicationBuilder builder, PathString pathPrefix)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

var webHostEnvironment = builder.ApplicationServices.GetRequiredService<IWebHostEnvironment>();

var options = CreateStaticFilesOptions(webHostEnvironment.WebRootFileProvider);

builder.UseWhen(ctx => ctx.Request.Path.StartsWithSegments(pathPrefix, out var rest),
subBuilder =>
{
subBuilder.Use(async (context, next) =>
{
context.Response.Headers.Append("UnoPlatform-Environment", webHostEnvironment.EnvironmentName);
if (webHostEnvironment.IsDevelopment())
{
// DOTNET_MODIFIABLE_ASSEMBLIES is used by the runtime to initialize hot-reload specific environment variables and is configured
// by the launching process (dotnet-watch / Visual Studio).
// In Development, we'll transmit the environment variable to WebAssembly as a HTTP header. The bootstrapping code will read the header
// and configure it as env variable for the wasm app.
if (Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES") is string dotnetModifiableAssemblies)
{
context.Response.Headers.Append("DOTNET-MODIFIABLE-ASSEMBLIES", dotnetModifiableAssemblies);
}
// See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000
// Translate the _ASPNETCORE_BROWSER_TOOLS environment configured by the browser tools agent in to a HTTP response header.
if (Environment.GetEnvironmentVariable("__ASPNETCORE_BROWSER_TOOLS") is string dotnetWasmHotReload)
{
context.Response.Headers.Append("ASPNETCORE-BROWSER-TOOLS", dotnetWasmHotReload);
}
}
await next(context);
});
subBuilder.UseStaticFiles(options);
});

return builder;
}

/// <summary>
/// Configures the application to serve Uno Platform WebAssembly framework files from the root path "/".
/// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
/// <returns>The <see cref="IApplicationBuilder"/></returns>
public static IApplicationBuilder UseUnoFrameworkFiles(this IApplicationBuilder applicationBuilder) =>
UseUnoFrameworkFiles(applicationBuilder, default);

private static StaticFileOptions CreateStaticFilesOptions(IFileProvider webRootFileProvider)
{
var options = new StaticFileOptions
{
FileProvider = webRootFileProvider
};

var contentTypeProvider = new FileExtensionContentTypeProvider();
AddMapping(contentTypeProvider, ".dll", MediaTypeNames.Application.Octet);
AddMapping(contentTypeProvider, ".clr", MediaTypeNames.Application.Octet);
AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet);
AddMapping(contentTypeProvider, ".br", MediaTypeNames.Application.Octet);
AddMapping(contentTypeProvider, ".dat", MediaTypeNames.Application.Octet);
AddMapping(contentTypeProvider, ".blat", MediaTypeNames.Application.Octet);

options.ContentTypeProvider = contentTypeProvider;

// Static files middleware will try to use application/x-gzip as the content
// type when serving a file with a gz extension. We need to correct that before
// sending the file.
options.OnPrepareResponse = fileContext =>
{
// At this point we mapped something from the /_framework
fileContext.Context.Response.Headers.Append(HeaderNames.CacheControl, "no-cache");
var requestPath = fileContext.Context.Request.Path;
var fileExtension = Path.GetExtension(requestPath.Value);
if (string.Equals(fileExtension, ".gz") || string.Equals(fileExtension, ".br"))
{
// When we are serving framework files (under _framework/ we perform content negotiation
// on the accept encoding and replace the path with <<original>>.gz|br if we can serve gzip or brotli content
// respectively.
// Here we simply calculate the original content type by removing the extension and apply it
// again.
// When we revisit this, we should consider calculating the original content type and storing it
// in the request along with the original target path so that we don't have to calculate it here.
var originalPath = Path.GetFileNameWithoutExtension(requestPath.Value);
if (originalPath != null && contentTypeProvider.TryGetContentType(originalPath, out var originalContentType))
{
fileContext.Context.Response.ContentType = originalContentType;
}
}
};

return options;
}

private static void AddMapping(FileExtensionContentTypeProvider provider, string name, string mimeType)
{
if (!provider.Mappings.ContainsKey(name))
{
provider.Mappings.Add(name, mimeType);
}
}
}

}
30 changes: 30 additions & 0 deletions src/Uno.Wasm.Bootstrap.Server/Uno.Wasm.Bootstrap.Server.csproj
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<NoWarn>1701;1702;1705;649</NoWarn>
<PackageId>Uno.Wasm.Bootstrap.Server</PackageId>
<GeneratePackageOnBuild Condition="'$(Configuration)'=='Release'">true</GeneratePackageOnBuild>
</PropertyGroup>

<PropertyGroup>
<Authors>nventive</Authors>
<PackageProjectUrl>https://github.com/nventive/Uno.Wasm.Bootstrap</PackageProjectUrl>
<PackageIconUrl>https://nv-assets.azurewebsites.net/logos/uno.png</PackageIconUrl>
<RepositoryUrl>https://github.com/nventive/Uno.Wasm.Bootstrap</RepositoryUrl>
<Description>Runtime server features for ASP.NET Core hosted Uno Platform applications.</Description>
<Copyright>Copyright (C) 2015-$([System.DateTime]::Now.ToString(`yyyy`)) Uno Platform inc. - all rights reserved</Copyright>
</PropertyGroup>

<ItemGroup>
<Content Include="build\Uno.Wasm.Bootstrap.Server.targets">
<PackagePath>build</PackagePath>
<Pack>true</Pack>
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="6.0.0" />
</ItemGroup>

</Project>
@@ -0,0 +1,3 @@
<Project>

</Project>

0 comments on commit c9bb187

Please sign in to comment.