Skip to content

Commit

Permalink
Fix static file handling so that it special cases modules.json (#18953)
Browse files Browse the repository at this point in the history
## Issue: Microsoft.AspNetCore.Components.WebView.props is not imported by Maui applications

This issue pertains to the non-importation of Microsoft.AspNetCore.Components.WebView.props by Maui applications. The file has been relocated within the package to ensure its correct pickup and importation during the restore/build process.

Fixes dotnet/aspnetcore#42348.

## Description

The inability of Maui applications to rely on JavaScript (JS) initializers, particularly in the case of Fluent UI, is impacted by this issue. The runtime searches for the initializer's definition in a specific location, which is configured in the props file of the package.

## Impact on Customers

The malfunction of JS initializers in Blazor Hybrid has significant implications for both library authors and end users.

Library authors may find their libraries' functionality restricted due to this bug. JS initializers, a feature in Blazor, enable library authors to inject scripts onto the page at the start of the app. These scripts can augment functionality, enhance user interfaces, or facilitate third-party service integration. For instance, a library author might employ a JS initializer to inject a script that integrates with a mapping service, thereby providing real-time location updates within a Blazor app. This functionality would be unavailable in Blazor Hybrid apps due to this bug.

End users may be unable to use certain libraries, or those libraries may not function as anticipated in Blazor Hybrid apps. If a user were to use a Blazor Hybrid app that relies on the aforementioned mapping library, they would not receive the real-time location updates that they would in a regular Blazor app. This could result in an inferior user experience, and in some cases, render the app unusable.

Users and library authors are compelled to manually inject the script onto the page, and some functionality (like configuring Blazor before it starts) is not available in this mode.

## Regression?

- [ ] Yes
- [X] No

## Risk

- [ ] High
- [ ] Medium
- [X] Low

The failure to load a file from a NuGet package impacts the build. The change causes the file to load at build time, enabling the rest of the pipeline to function as expected.

## Verification

- [X] Manual (required)
- [ ] Automated

The changes were made locally on the package cache and ensured the file got imported.

## Packaging changes reviewed?

- [ ] Yes
- [ ] No
- [x] N/A

## When servicing release/2.1

- [ ] Make necessary changes in eng/PatchConfig.props
  • Loading branch information
javiercn authored Nov 27, 2023
1 parent 34d73c8 commit 4a31ee1
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const element = document.createElement('p');
element.innerHTML = 'Hello from Razor Class Library';
document.body.appendChild(element);
150 changes: 89 additions & 61 deletions src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
Expand Down Expand Up @@ -84,86 +85,113 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe

_logger.HandlingWebRequest(requestUri);

// First, call into WebViewManager to see if it has a framework file for this request. It will
// fall back to an IFileProvider, but on WinUI it's always a NullFileProvider, so that will never
// return a file.
if (TryGetResponseContent(requestUri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)
var uri = new Uri(requestUri);
var relativePath = AppOriginUri.IsBaseOf(uri) ? AppOriginUri.MakeRelativeUri(uri).ToString() : null;

// Check if the uri is _framework/blazor.modules.json is a special case as the built-in file provider
// brings in a default implementation.
if (relativePath != null &&
string.Equals(relativePath, "_framework/blazor.modules.json", StringComparison.Ordinal) &&
await TryServeFromFolderAsync(eventArgs, allowFallbackOnHostPage: false, requestUri, relativePath))
{
_logger.ResponseContentBeingSent(requestUri, 200);
}
else if (TryGetResponseContent(requestUri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)
&& statusCode != 404)
{
// First, call into WebViewManager to see if it has a framework file for this request. It will
// fall back to an IFileProvider, but on WinUI it's always a NullFileProvider, so that will never
// return a file.
var headerString = GetHeaderString(headers);
_logger.ResponseContentBeingSent(requestUri, statusCode);
eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(content.AsRandomAccessStream(), statusCode, statusMessage, headerString);
}
else if (new Uri(requestUri) is Uri uri && AppOriginUri.IsBaseOf(uri))
else if (relativePath != null)
{
var relativePath = AppOriginUri.MakeRelativeUri(uri).ToString();
await TryServeFromFolderAsync(
eventArgs,
allowFallbackOnHostPage,
requestUri,
relativePath);
}

// If the path does not end in a file extension (or is empty), it's most likely referring to a page,
// in which case we should allow falling back on the host page.
if (allowFallbackOnHostPage && !Path.HasExtension(relativePath))
{
relativePath = _hostPageRelativePath;
}
relativePath = Path.Combine(_contentRootRelativeToAppRoot, relativePath.Replace('/', '\\'));
statusCode = 200;
statusMessage = "OK";
var contentType = StaticContentProvider.GetResponseContentTypeOrDefault(relativePath);
headers = StaticContentProvider.GetResponseHeaders(contentType);
IRandomAccessStream? stream = null;
if (_isPackagedApp)
// Notify WebView2 that the deferred (async) operation is complete and we set a response.
deferral.Complete();
}

private async Task<bool> TryServeFromFolderAsync(
CoreWebView2WebResourceRequestedEventArgs eventArgs,
bool allowFallbackOnHostPage,
string requestUri,
string relativePath)
{
// If the path does not end in a file extension (or is empty), it's most likely referring to a page,
// in which case we should allow falling back on the host page.
if (allowFallbackOnHostPage && !Path.HasExtension(relativePath))
{
relativePath = _hostPageRelativePath;
}
relativePath = Path.Combine(_contentRootRelativeToAppRoot, relativePath.Replace('/', '\\'));
var statusCode = 200;
var statusMessage = "OK";
var contentType = StaticContentProvider.GetResponseContentTypeOrDefault(relativePath);
var headers = StaticContentProvider.GetResponseHeaders(contentType);
IRandomAccessStream? stream = null;
if (_isPackagedApp)
{
var winUIItem = await Package.Current.InstalledLocation.TryGetItemAsync(relativePath);
var location = Package.Current.InstalledLocation.Path;
if (winUIItem != null)
{
var winUIItem = await Package.Current.InstalledLocation.TryGetItemAsync(relativePath);
if (winUIItem != null)
{
using var contentStream = await Package.Current.InstalledLocation.OpenStreamForReadAsync(relativePath);
stream = await CopyContentToRandomAccessStreamAsync(contentStream);
}
using var contentStream = await Package.Current.InstalledLocation.OpenStreamForReadAsync(relativePath);
stream = await CopyContentToRandomAccessStreamAsync(contentStream);
}
else
}
else
{
var path = Path.Combine(AppContext.BaseDirectory, relativePath);
if (File.Exists(path))
{
var path = Path.Combine(AppContext.BaseDirectory, relativePath);
if (File.Exists(path))
{
using var contentStream = File.OpenRead(path);
stream = await CopyContentToRandomAccessStreamAsync(contentStream);
}
using var contentStream = File.OpenRead(path);
stream = await CopyContentToRandomAccessStreamAsync(contentStream);
}
}

var hotReloadedContent = Stream.Null;
if (StaticContentHotReloadManager.TryReplaceResponseContent(_contentRootRelativeToAppRoot, requestUri, ref statusCode, ref hotReloadedContent, headers))
{
stream = await CopyContentToRandomAccessStreamAsync(hotReloadedContent);
}
var hotReloadedContent = Stream.Null;
if (StaticContentHotReloadManager.TryReplaceResponseContent(_contentRootRelativeToAppRoot, requestUri, ref statusCode, ref hotReloadedContent, headers))
{
stream = await CopyContentToRandomAccessStreamAsync(hotReloadedContent);
}

if (stream != null)
{
var headerString = GetHeaderString(headers);
if (stream != null)
{
var headerString = GetHeaderString(headers);

_logger.ResponseContentBeingSent(requestUri, statusCode);
_logger.ResponseContentBeingSent(requestUri, statusCode);

eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(
stream,
statusCode,
statusMessage,
headerString);
}
else
{
_logger.ReponseContentNotFound(requestUri);
}
eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(
stream,
statusCode,
statusMessage,
headerString);

async Task<IRandomAccessStream> CopyContentToRandomAccessStreamAsync(Stream content)
{
using var memStream = new MemoryStream();
await content.CopyToAsync(memStream);
var randomAccessStream = new InMemoryRandomAccessStream();
await randomAccessStream.WriteAsync(memStream.GetWindowsRuntimeBuffer());
return randomAccessStream;
}
return true;
}
else
{
_logger.ReponseContentNotFound(requestUri);
}

// Notify WebView2 that the deferred (async) operation is complete and we set a response.
deferral.Complete();
return false;

async Task<IRandomAccessStream> CopyContentToRandomAccessStreamAsync(Stream content)
{
using var memStream = new MemoryStream();
await content.CopyToAsync(memStream);
var randomAccessStream = new InMemoryRandomAccessStream();
await randomAccessStream.WriteAsync(memStream.GetWindowsRuntimeBuffer());
return randomAccessStream;
}
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const element = document.createElement('p');
element.innerHTML = 'Hello from App';
document.body.appendChild(element);

0 comments on commit 4a31ee1

Please sign in to comment.