Skip to content

Blazor WebView alternate runtime platform #33413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebView.Test", "src\Components\WebView\WebView\test\Microsoft.AspNetCore.Components.WebView.Test.csproj", "{4BD6F0DB-BE9C-4C54-B52A-D20B88855ED5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{B730328F-D9E9-4EAA-B28E-4631A14095F9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PhotinoPlatform", "PhotinoPlatform", "{44963D50-8B58-44E6-918D-788BCB406695}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{3EC71A0E-6515-4A5A-B759-F0BCF1BCFC56}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotinoTestApp", "src\Components\WebView\Samples\PhotinoPlatform\testassets\PhotinoTestApp\PhotinoTestApp.csproj", "{558C46DE-DE16-41D5-8DB7-D6D748E32977}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.WebView.Photino", "src\Components\WebView\Samples\PhotinoPlatform\src\Microsoft.AspNetCore.Components.WebView.Photino.csproj", "{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -7683,6 +7693,30 @@ Global
{4BD6F0DB-BE9C-4C54-B52A-D20B88855ED5}.Release|x64.Build.0 = Release|Any CPU
{4BD6F0DB-BE9C-4C54-B52A-D20B88855ED5}.Release|x86.ActiveCfg = Release|Any CPU
{4BD6F0DB-BE9C-4C54-B52A-D20B88855ED5}.Release|x86.Build.0 = Release|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Debug|Any CPU.Build.0 = Debug|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Debug|x64.ActiveCfg = Debug|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Debug|x64.Build.0 = Debug|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Debug|x86.ActiveCfg = Debug|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Debug|x86.Build.0 = Debug|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Release|Any CPU.ActiveCfg = Release|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Release|Any CPU.Build.0 = Release|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Release|x64.ActiveCfg = Release|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Release|x64.Build.0 = Release|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Release|x86.ActiveCfg = Release|Any CPU
{558C46DE-DE16-41D5-8DB7-D6D748E32977}.Release|x86.Build.0 = Release|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Debug|x64.ActiveCfg = Debug|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Debug|x64.Build.0 = Debug|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Debug|x86.ActiveCfg = Debug|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Debug|x86.Build.0 = Debug|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Release|Any CPU.Build.0 = Release|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Release|x64.ActiveCfg = Release|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Release|x64.Build.0 = Release|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Release|x86.ActiveCfg = Release|Any CPU
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -8481,6 +8515,11 @@ Global
{A1D02CE6-1077-410A-81CB-D4BD500FD765} = {0508E463-0269-40C9-B5C2-3B600FB2A28B}
{3044DFA5-DE4F-44D8-8DD8-EDF547BE513E} = {C445B129-0A4D-41F5-8347-6534B6B12303}
{4BD6F0DB-BE9C-4C54-B52A-D20B88855ED5} = {C445B129-0A4D-41F5-8347-6534B6B12303}
{B730328F-D9E9-4EAA-B28E-4631A14095F9} = {C445B129-0A4D-41F5-8347-6534B6B12303}
{44963D50-8B58-44E6-918D-788BCB406695} = {B730328F-D9E9-4EAA-B28E-4631A14095F9}
{3EC71A0E-6515-4A5A-B759-F0BCF1BCFC56} = {44963D50-8B58-44E6-918D-788BCB406695}
{558C46DE-DE16-41D5-8DB7-D6D748E32977} = {3EC71A0E-6515-4A5A-B759-F0BCF1BCFC56}
{B1AA24A4-5E02-4DC1-B57F-6EB03F91E4DD} = {44963D50-8B58-44E6-918D-788BCB406695}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
Expand Down
1 change: 1 addition & 0 deletions eng/Dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="NSwag.ApiDescription.Client" />
<LatestPackageReference Include="NuGet.Frameworks" />
<LatestPackageReference Include="NuGet.Versioning" />
<LatestPackageReference Include="Photino.NET" />
<LatestPackageReference Include="PlaywrightSharp" />
<LatestPackageReference Include="Polly" />
<LatestPackageReference Include="Polly.Extensions.Http" />
Expand Down
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@
<NewtonsoftJsonBsonVersion>1.0.2</NewtonsoftJsonBsonVersion>
<NewtonsoftJsonVersion>12.0.2</NewtonsoftJsonVersion>
<NSwagApiDescriptionClientVersion>13.0.4</NSwagApiDescriptionClientVersion>
<PhotinoNETVersion>1.1.6</PhotinoNETVersion>
<PlaywrightSharpVersion>0.192.0</PlaywrightSharpVersion>
<PollyExtensionsHttpVersion>3.0.0</PollyExtensionsHttpVersion>
<PollyVersion>7.2.2</PollyVersion>
Expand Down
2 changes: 2 additions & 0 deletions src/Components/ComponentsNoDeps.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
"src\\Components\\WebAssembly\\testassets\\Wasm.Authentication.Server\\Wasm.Authentication.Server.csproj",
"src\\Components\\WebAssembly\\testassets\\Wasm.Authentication.Shared\\Wasm.Authentication.Shared.csproj",
"src\\Components\\WebAssembly\\testassets\\WasmLinkerTest\\WasmLinkerTest.csproj",
"src\\Components\\WebView\\Samples\\PhotinoPlatform\\src\\Microsoft.AspNetCore.Components.WebView.Photino.csproj",
"src\\Components\\WebView\\Samples\\PhotinoPlatform\\testassets\\PhotinoTestApp\\PhotinoTestApp.csproj",
"src\\Components\\WebView\\WebView\\src\\Microsoft.AspNetCore.Components.WebView.csproj",
"src\\Components\\WebView\\WebView\\test\\Microsoft.AspNetCore.Components.WebView.Test.csproj",
"src\\Components\\Web\\src\\Microsoft.AspNetCore.Components.Web.csproj",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.FileProviders;
using PhotinoNET;

namespace Microsoft.AspNetCore.Components.WebView.Photino
{
/// <summary>
/// A window containing a Blazor web view.
/// </summary>
public class BlazorWindow
{
private readonly PhotinoWindow _window;
private readonly PhotinoWebViewManager _manager;

/// <summary>
/// Constructs an instance of <see cref="BlazorWindow"/>.
/// </summary>
/// <param name="title">The window title.</param>
/// <param name="hostPage">The path to the host page.</param>
/// <param name="services">The service provider.</param>
/// <param name="configureWindow">A callback that configures the window.</param>
public BlazorWindow(
string title,
string hostPage,
IServiceProvider services,
Action<PhotinoWindowOptions>? configureWindow = null)
{
_window = new PhotinoWindow(title, options =>
{
options.CustomSchemeHandlers.Add(PhotinoWebViewManager.BlazorAppScheme, HandleWebRequest);
configureWindow?.Invoke(options);
}, width: 1600, height: 1200, left: 300, top: 300);

// We assume the host page is always in the root of the content directory, because it's
// unclear there's any other use case. We can add more options later if so.
var contentRootDir = Path.GetDirectoryName(Path.GetFullPath(hostPage))!;
var hostPageRelativePath = Path.GetRelativePath(contentRootDir, hostPage);
var fileProvider = new PhysicalFileProvider(contentRootDir);

var dispatcher = new PhotinoDispatcher(_window);
_manager = new PhotinoWebViewManager(_window, services, dispatcher, new Uri(PhotinoWebViewManager.AppBaseUri), fileProvider, hostPageRelativePath);
}

/// <summary>
/// Gets the underlying <see cref="PhotinoWindow"/>.
/// </summary>
public PhotinoWindow Photino => _window;

/// <summary>
/// Adds a root component to the window.
/// </summary>
/// <typeparam name="TComponent">The component type.</typeparam>
/// <param name="selector">A CSS selector describing where the component should be added in the host page.</param>
/// <param name="parameters">An optional dictionary of parameters to pass to the component.</param>
public void AddRootComponent<TComponent>(string selector, IDictionary<string, object?>? parameters = null) where TComponent: IComponent
{
var parameterView = parameters == null
? ParameterView.Empty
: ParameterView.FromDictionary(parameters);

// Dispatch because this is going to be async, and we want to catch any errors
_ = _manager.Dispatcher.InvokeAsync(async () =>
{
await _manager.AddRootComponentAsync(typeof(TComponent), selector, parameterView);
});
}

/// <summary>
/// Shows the window and waits for it to be closed.
/// </summary>
public void Run()
{
_manager.Navigate("/");
_window.WaitForClose();
}

private Stream HandleWebRequest(string url, out string contentType)
=> _manager.HandleWebRequest(url, out contentType!)!;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<Description>Intended for internal testing use only.</Description>
<IsShippingPackage>false</IsShippingPackage>
<SignAssembly>false</SignAssembly>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Components.WebView" />
<Reference Include="Photino.NET" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Threading;
using System.Threading.Tasks;
using PhotinoNET;

namespace Microsoft.AspNetCore.Components.WebView.Photino
{
internal class PhotinoDispatcher : Dispatcher
{
private readonly PhotinoSynchronizationContext _context;

public PhotinoDispatcher(PhotinoWindow window)
{
_context = new PhotinoSynchronizationContext(window);
_context.UnhandledException += (sender, e) =>
{
OnUnhandledException(e);
};
}

public override bool CheckAccess() => SynchronizationContext.Current == _context;

public override Task InvokeAsync(Action workItem)
{
if (CheckAccess())
{
workItem();
return Task.CompletedTask;
}

return _context.InvokeAsync(workItem);
}

public override Task InvokeAsync(Func<Task> workItem)
{
if (CheckAccess())
{
return workItem();
}

return _context.InvokeAsync(workItem);
}

public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
{
if (CheckAccess())
{
return Task.FromResult(workItem());
}

return _context.InvokeAsync<TResult>(workItem);
}

public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
{
if (CheckAccess())
{
return workItem();
}

return _context.InvokeAsync<TResult>(workItem);
}
}
}
Loading