From 2f2f7c0c5376da00f5409e9de60fe986f5bce02c Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Thu, 14 Apr 2022 12:24:22 +0100 Subject: [PATCH] Properly account for configurable content roots --- .../Android/AndroidWebKitWebViewManager.cs | 7 +- .../Android/BlazorWebViewHandler.Android.cs | 1 + .../Windows/BlazorWebViewHandler.Windows.cs | 2 +- .../src/Maui/Windows/WinUIWebViewManager.cs | 22 +-- .../src/Maui/iOS/BlazorWebViewHandler.iOS.cs | 1 + .../src/Maui/iOS/IOSWebViewManager.cs | 7 +- .../StaticContentHotReloadManager.cs | 139 ++++++++++-------- .../SharedSource/WebView2WebViewManager.cs | 21 ++- .../src/WindowsForms/BlazorWebView.cs | 4 +- src/BlazorWebView/src/Wpf/BlazorWebView.cs | 4 +- 10 files changed, 122 insertions(+), 86 deletions(-) diff --git a/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs b/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs index 2c313949b184..b056f1345e69 100644 --- a/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Android/AndroidWebKitWebViewManager.cs @@ -23,6 +23,7 @@ internal class AndroidWebKitWebViewManager : WebViewManager private static readonly string AppOrigin = $"https://{BlazorWebView.AppHostAddress}/"; private static readonly AUri AndroidAppOriginUri = AUri.Parse(AppOrigin)!; private readonly AWebView _webview; + private readonly string _contentRootRelativeToAppRoot; private WebMessagePort[]? _nativeToJSPorts; /// @@ -32,8 +33,9 @@ internal class AndroidWebKitWebViewManager : WebViewManager /// A service provider containing services to be used by this class and also by application code. /// A instance that can marshal calls to the required thread or sync context. /// Provides static content to the webview. + /// Path to the directory containing application content files. /// Path to the host page within the . - public AndroidWebKitWebViewManager(AWebView webview!!, IServiceProvider services, Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, string hostPageRelativePath) + public AndroidWebKitWebViewManager(AWebView webview!!, IServiceProvider services, Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, string contentRootRelativeToAppRoot, string hostPageRelativePath) : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath) { #if WEBVIEW2_MAUI @@ -45,6 +47,7 @@ public AndroidWebKitWebViewManager(AWebView webview!!, IServiceProvider services } #endif _webview = webview; + _contentRootRelativeToAppRoot = contentRootRelativeToAppRoot; } /// @@ -62,7 +65,7 @@ protected override void SendMessage(string message) internal bool TryGetResponseContentInternal(string uri, bool allowFallbackOnHostPage, out int statusCode, out string statusMessage, out Stream content, out IDictionary headers) { var defaultResult = TryGetResponseContent(uri, allowFallbackOnHostPage, out statusCode, out statusMessage, out content, out headers); - var hotReloadedResult = StaticContentHotReloadManager.TryReplaceResponseContent(uri, ref statusCode, ref content, headers); + var hotReloadedResult = StaticContentHotReloadManager.TryReplaceResponseContent(_contentRootRelativeToAppRoot, uri, ref statusCode, ref content, headers); return defaultResult || hotReloadedResult; } diff --git a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs index f8235153e7e5..f69043263fd7 100644 --- a/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs +++ b/src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs @@ -97,6 +97,7 @@ private void StartWebViewCoreIfPossible() new MauiDispatcher(Services!.GetRequiredService()), fileProvider, VirtualView.JSComponents, + contentRootDir, hostPageRelativePath); StaticContentHotReloadManager.AttachToWebViewManagerIfEnabled(_webviewManager, WebKitWebViewClient.AppOrigin); diff --git a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs index dd9f908520c9..fa75fb952fdd 100644 --- a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs +++ b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs @@ -69,8 +69,8 @@ private void StartWebViewCoreIfPossible() new MauiDispatcher(Services!.GetRequiredService()), fileProvider, VirtualView.JSComponents, - hostPageRelativePath, contentRootDir, + hostPageRelativePath, this); StaticContentHotReloadManager.AttachToWebViewManagerIfEnabled(_webviewManager, WebView2WebViewManager.AppOrigin); diff --git a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs index 80779d1669b6..825f394728ec 100644 --- a/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/Windows/WinUIWebViewManager.cs @@ -20,7 +20,7 @@ internal class WinUIWebViewManager : WebView2WebViewManager { private readonly WebView2Control _webview; private readonly string _hostPageRelativePath; - private readonly string _contentRootDir; + private readonly string _contentRootRelativeToAppRoot; private static readonly bool _isPackagedApp; static WinUIWebViewManager() @@ -43,8 +43,8 @@ static WinUIWebViewManager() /// A instance that can marshal calls to the required thread or sync context. /// Provides static content to the webview. /// The . - /// Path to the host page within the . - /// Path to the directory containing application content files. + /// Path to the directory containing application content files. + /// Path to the host page within the . /// The . public WinUIWebViewManager( WebView2Control webview, @@ -52,16 +52,16 @@ public WinUIWebViewManager( Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, - string hostPageRelativePath, - string contentRootDir, + string contentRootRelativeToAppRoot, + string hostPagePathWithinFileProvider, BlazorWebViewHandler webViewHandler) - : base(webview, services, dispatcher, fileProvider, jsComponents, hostPageRelativePath, webViewHandler) + : base(webview, services, dispatcher, fileProvider, jsComponents, contentRootRelativeToAppRoot, hostPagePathWithinFileProvider, webViewHandler) { _webview = webview; - _hostPageRelativePath = hostPageRelativePath; - _contentRootDir = contentRootDir; + _hostPageRelativePath = hostPagePathWithinFileProvider; + _contentRootRelativeToAppRoot = contentRootRelativeToAppRoot; } - + /// protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRequestedEventArgs eventArgs) { @@ -103,7 +103,7 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe { relativePath = _hostPageRelativePath; } - relativePath = Path.Combine(_contentRootDir, relativePath.Replace('/', '\\')); + relativePath = Path.Combine(_contentRootRelativeToAppRoot, relativePath.Replace('/', '\\')); statusCode = 200; statusMessage = "OK"; var contentType = StaticContentProvider.GetResponseContentTypeOrDefault(relativePath); @@ -134,7 +134,7 @@ protected override async Task HandleWebResourceRequest(CoreWebView2WebResourceRe } var hotReloadedContent = Stream.Null; - if (StaticContentHotReloadManager.TryReplaceResponseContent(requestUri, ref statusCode, ref hotReloadedContent, headers)) + if (StaticContentHotReloadManager.TryReplaceResponseContent(_contentRootRelativeToAppRoot, requestUri, ref statusCode, ref hotReloadedContent, headers)) { stream = new InMemoryRandomAccessStream(); var memStream = new MemoryStream(); diff --git a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs index 18932c15fe08..2cfa7de42347 100644 --- a/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs +++ b/src/BlazorWebView/src/Maui/iOS/BlazorWebViewHandler.iOS.cs @@ -134,6 +134,7 @@ private void StartWebViewCoreIfPossible() new MauiDispatcher(Services!.GetRequiredService()), fileProvider, VirtualView.JSComponents, + contentRootDir, hostPageRelativePath); StaticContentHotReloadManager.AttachToWebViewManagerIfEnabled(_webviewManager, AppOrigin); diff --git a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs index afdec41e127f..16d9bedc5d2e 100644 --- a/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs +++ b/src/BlazorWebView/src/Maui/iOS/IOSWebViewManager.cs @@ -20,6 +20,7 @@ internal class IOSWebViewManager : WebViewManager { private readonly BlazorWebViewHandler _blazorMauiWebViewHandler; private readonly WKWebView _webview; + private readonly string _contentRootRelativeToAppRoot; /// /// Initializes a new instance of @@ -30,8 +31,9 @@ internal class IOSWebViewManager : WebViewManager /// A instance instance that can marshal calls to the required thread or sync context. /// Provides static content to the webview. /// Describes configuration for adding, removing, and updating root components from JavaScript code. + /// Path to the directory containing application content files. /// Path to the host page within the fileProvider. - public IOSWebViewManager(BlazorWebViewHandler blazorMauiWebViewHandler!!, WKWebView webview!!, IServiceProvider provider, Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, string hostPageRelativePath) + public IOSWebViewManager(BlazorWebViewHandler blazorMauiWebViewHandler!!, WKWebView webview!!, IServiceProvider provider, Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, string contentRootRelativeToAppRoot, string hostPageRelativePath) : base(provider, dispatcher, new Uri(BlazorWebViewHandler.AppOrigin), fileProvider, jsComponents, hostPageRelativePath) { if (provider.GetService() is null) @@ -43,6 +45,7 @@ public IOSWebViewManager(BlazorWebViewHandler blazorMauiWebViewHandler!!, WKWebV _blazorMauiWebViewHandler = blazorMauiWebViewHandler; _webview = webview; + _contentRootRelativeToAppRoot = contentRootRelativeToAppRoot; InitializeWebView(); } @@ -58,7 +61,7 @@ protected override void NavigateCore(Uri absoluteUri) internal bool TryGetResponseContentInternal(string uri, bool allowFallbackOnHostPage, out int statusCode, out string statusMessage, out Stream content, out IDictionary headers) { var defaultResult = TryGetResponseContent(uri, allowFallbackOnHostPage, out statusCode, out statusMessage, out content, out headers); - var hotReloadedResult = StaticContentHotReloadManager.TryReplaceResponseContent(uri, ref statusCode, ref content, headers); + var hotReloadedResult = StaticContentHotReloadManager.TryReplaceResponseContent(_contentRootRelativeToAppRoot, uri, ref statusCode, ref content, headers); return defaultResult || hotReloadedResult; } diff --git a/src/BlazorWebView/src/SharedSource/StaticContentHotReloadManager.cs b/src/BlazorWebView/src/SharedSource/StaticContentHotReloadManager.cs index 6a6583100d0e..b8f706592fbb 100644 --- a/src/BlazorWebView/src/SharedSource/StaticContentHotReloadManager.cs +++ b/src/BlazorWebView/src/SharedSource/StaticContentHotReloadManager.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Reflection.Metadata; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.JSInterop; @@ -24,100 +25,94 @@ public static void UpdateContent(string assemblyName, string relativePath, byte[ internal static class StaticContentHotReloadManager { - private delegate void ContentUpdatedHandler(string url); + private delegate void ContentUpdatedHandler(string assemblyName, string relativePath); private static event ContentUpdatedHandler? OnContentUpdated; - private static string AppOrigin = default!; - private static readonly Dictionary _updatedContentByAbsoluteUrl = new(StringComparer.Ordinal); + private static string ApplicationAssemblyName { get; } = +#if MAUI + Application.Context.PackageName; +#else + Assembly.GetEntryAssembly()!.GetName().Name!; +#endif - private static readonly string StaticContentHotReloadModuleSource = @" - export function notifyContentUpdated(url) { + private static readonly Dictionary<(string AssemblyName, string RelativePath), (string? ContentType, byte[] Content)> _updatedContent = new() + { + { (ApplicationAssemblyName, "_framework/static-content-hot-reload.js"), ("text/javascript", Encoding.UTF8.GetBytes(@" + export function notifyContentUpdated(urlWithinOrigin) { const allLinkElems = Array.from(document.querySelectorAll('link[rel=stylesheet]')); - const matchingLinkElems = allLinkElems.filter(x => x.href === url); + const absoluteUrl = document.location.origin + urlWithinOrigin; + const matchingLinkElems = allLinkElems.filter(x => x.href === absoluteUrl); // If we can't find a matching link element, that probably means it's a CSS file imported via @import // from some other CSS file. We can't know which other file imports it, so refresh them all. - const linkElemsToUpdate = matchingLinkElems.length > 0 || !url.endsWith('.css') + const linkElemsToUpdate = matchingLinkElems.length > 0 || !absoluteUrl.endsWith('.css') ? matchingLinkElems : allLinkElems; linkElemsToUpdate.forEach(tag => tag.href += ''); } -"; +")) } + }; /// /// MetadataUpdateHandler event. This is invoked by the hot reload host via reflection. /// public static void UpdateContent(string assemblyName, string relativePath, byte[] content) { - var absoluteUrl = GetAbsoluteUrlForStaticContent(assemblyName, relativePath); - _updatedContentByAbsoluteUrl[absoluteUrl] = (ContentType: null, Content: content); - OnContentUpdated?.Invoke(absoluteUrl); + _updatedContent[(assemblyName, relativePath)] = (ContentType: null, Content: content); + OnContentUpdated?.Invoke(assemblyName, relativePath); } - public static void AttachToWebViewManagerIfEnabled(WebViewManager manager, string appOrigin) + public static void AttachToWebViewManagerIfEnabled(WebViewManager manager, string assemblyName, string contentRoot) { if (MetadataUpdater.IsSupported) { - AppOrigin = appOrigin; - _updatedContentByAbsoluteUrl[AppOrigin + "_framework/static-content-hot-reload.js"] = - (ContentType: "text/javascript", Content: Encoding.UTF8.GetBytes(StaticContentHotReloadModuleSource)); - - manager.AddRootComponentAsync(typeof(StaticContentUpdater), "body::after", ParameterView.Empty); + var parameters = new Dictionary { { nameof(StaticContentUpdater.ContentRoot), contentRoot } }; + manager.AddRootComponentAsync(typeof(StaticContentUpdater), "body::after", ParameterView.FromDictionary(parameters)); } } - public static bool TryReplaceResponseContent(string requestAbsoluteUri, ref int responseStatusCode, ref Stream responseContent, IDictionary responseHeaders) + public static bool TryReplaceResponseContent(string contentRootRelativePath, string requestAbsoluteUri, ref int responseStatusCode, ref Stream responseContent, IDictionary responseHeaders) { - if (MetadataUpdater.IsSupported && _updatedContentByAbsoluteUrl.TryGetValue(requestAbsoluteUri, out var values)) + if (MetadataUpdater.IsSupported) { - responseStatusCode = 200; - responseContent = new MemoryStream(values.Content); - if (!string.IsNullOrEmpty(values.ContentType)) + var (assemblyName, relativePath) = GetAssemblyNameAndRelativePath(requestAbsoluteUri, contentRootRelativePath); + if (_updatedContent.TryGetValue((assemblyName, relativePath), out var values)) { - responseHeaders["Content-Type"] = values.ContentType; + responseStatusCode = 200; + responseContent = new MemoryStream(values.Content); + if (!string.IsNullOrEmpty(values.ContentType)) + { + responseHeaders["Content-Type"] = values.ContentType; + } + + return true; } - - return true; - } - else - { - return false; } + + return false; } + + private readonly static Regex ContentUrlRegex = new Regex("^_content/(?[^/]+)/(?.*)"); - private static string GetAbsoluteUrlForStaticContent(string assemblyName, string relativePath) + private static (string AssemblyName, string RelativePath) GetAssemblyNameAndRelativePath(string requestAbsoluteUri, string appContentRoot) { - // This logic might not cover every circumstance if the developer customizes the host page path - // or is doing something custom with static web assets. However it should cover any mainstream - // case with single projects and RCLs. We may have to allow for other cases in the future, or - // may have to receive different information from tooling. - - // Since scoped CSS bundles might not have a wwwroot prefix, normalize by removing it. - // Whether this is really needed depends on tooling implementations that are not yet known. - const string wwwrootPrefix = "wwwroot/"; - if (relativePath.StartsWith(wwwrootPrefix, StringComparison.Ordinal)) + var requestPath = new Uri(requestAbsoluteUri).AbsolutePath.Substring(1); + if (ContentUrlRegex.Match(requestPath) is { Success: true } match) { - relativePath = relativePath.Substring(wwwrootPrefix.Length); + // For RCLs (i.e., URLs of the form _content/assembly/path), we assume the content root within the + // RCL to be "wwwroot" since we have no other information. If this is not the case, content within + // that RCL will not be hot-reloadable. + return (match.Groups["AssemblyName"].Value, $"wwwroot/{match.Groups["RelativePath"].Value}"); } - - if (relativePath.StartsWith("/")) + else if (requestPath.StartsWith("_framework/", StringComparison.Ordinal)) { - relativePath = relativePath.Substring(1); + return (ApplicationAssemblyName, requestPath); } - - // SWA convention for RCLs - // Note that on Android, entryAssembly will be null, so we have no way to know if the file comes from an RCL or not. - // As a temporary stage, Android will treat all content as if it is *not* from an RCL, which unfortunately means we - // won't be able to hot-reload CSS from an RCL on Android. - var entryAssembly = Assembly.GetEntryAssembly(); - if (entryAssembly is not null - && !string.Equals(assemblyName, entryAssembly.GetName().Name, StringComparison.Ordinal)) + else { - relativePath = $"_content/{assemblyName}/{relativePath}"; + return (ApplicationAssemblyName, Path.Combine(appContentRoot, requestPath).Replace('\\', '/')); } - - return AppOrigin + relativePath; } // To provide a consistent way of transporting the data across all platforms, @@ -126,9 +121,11 @@ private static string GetAbsoluteUrlForStaticContent(string assemblyName, string // by injecting this headless root component. private sealed class StaticContentUpdater : IComponent, IDisposable { + private ILogger _logger = default!; + [Inject] private IJSRuntime JSRuntime { get; set; } = default!; [Inject] private ILoggerFactory LoggerFactory { get; set; } = default!; - private ILogger _logger = default!; + [Parameter] public string ContentRoot { get; set; } = default!; public void Attach(RenderHandle renderHandle) { @@ -141,27 +138,47 @@ public void Dispose() OnContentUpdated -= NotifyContentUpdated; } - private void NotifyContentUpdated(string url) + private void NotifyContentUpdated(string assemblyName, string relativePath) { // It handles its own errors - _ = NotifyContentUpdatedAsync(url); + _ = NotifyContentUpdatedAsync(assemblyName, relativePath); } - private async Task NotifyContentUpdatedAsync(string url) + private async Task NotifyContentUpdatedAsync(string assemblyName, string relativePath) { try { await using var module = await JSRuntime.InvokeAsync("import", "./_framework/static-content-hot-reload.js"); - await module.InvokeVoidAsync("notifyContentUpdated", url); + + if (string.Equals(assemblyName, ApplicationAssemblyName, StringComparison.Ordinal)) + { + if (relativePath.StartsWith(ContentRoot + "/", StringComparison.Ordinal)) + { + var pathWithinContentRoot = relativePath.Substring(ContentRoot.Length); + await module.InvokeVoidAsync("notifyContentUpdated", pathWithinContentRoot); + } + } + else + { + if (relativePath.StartsWith("wwwroot/", StringComparison.Ordinal)) + { + var pathWithinContentRoot = relativePath.Substring("wwwroot/".Length); + await module.InvokeVoidAsync("notifyContentUpdated", $"/_content/{assemblyName}/{pathWithinContentRoot}"); + } + } + } catch (Exception ex) { - _logger.LogError(ex, $"Failed to notify about static content update to {url}."); + _logger.LogError(ex, $"Failed to notify about static content update to {relativePath}."); } } public Task SetParametersAsync(ParameterView parameters) - => Task.CompletedTask; + { + parameters.SetParameterProperties(this); + return Task.CompletedTask; + } } } } diff --git a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs index 8e8ae15e9392..198811c73f40 100644 --- a/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/SharedSource/WebView2WebViewManager.cs @@ -63,6 +63,7 @@ internal class WebView2WebViewManager : WebViewManager private readonly WebView2Control _webview; private readonly Task _webviewReadyTask; + private readonly string _contentRootRelativeToAppRoot; #if WEBVIEW2_WINFORMS || WEBVIEW2_WPF private protected CoreWebView2Environment? _coreWebView2Environment; @@ -79,7 +80,8 @@ internal class WebView2WebViewManager : WebViewManager /// A instance that can marshal calls to the required thread or sync context. /// Provides static content to the webview. /// Describes configuration for adding, removing, and updating root components from JavaScript code. - /// Path to the host page within the . + /// Path to the app's content root relative to the application root directory. + /// Path to the host page within the . /// Callback invoked when a url is about to load. /// Callback invoked before the webview is initialized. /// Callback invoked after the webview is initialized. @@ -89,11 +91,12 @@ internal WebView2WebViewManager( Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, - string hostPageRelativePath, + string contentRootRelativeToAppRoot, + string hostPagePathWithinFileProvider, Action urlLoading, Action blazorWebViewInitializing, Action blazorWebViewInitialized) - : base(services, dispatcher, AppOriginUri, fileProvider, jsComponents, hostPageRelativePath) + : base(services, dispatcher, AppOriginUri, fileProvider, jsComponents, hostPagePathWithinFileProvider) { #if WEBVIEW2_WINFORMS @@ -117,6 +120,7 @@ internal WebView2WebViewManager( _blazorWebViewInitializing = blazorWebViewInitializing; _blazorWebViewInitialized = blazorWebViewInitialized; _developerTools = services.GetRequiredService(); + _contentRootRelativeToAppRoot = contentRootRelativeToAppRoot; // Unfortunately the CoreWebView2 can only be instantiated asynchronously. // We want the external API to behave as if initalization is synchronous, @@ -135,7 +139,8 @@ internal WebView2WebViewManager( /// A instance that can marshal calls to the required thread or sync context. /// Provides static content to the webview. /// Describes configuration for adding, removing, and updating root components from JavaScript code. - /// Path to the host page within the . + /// Path to the app's content root relative to the application root directory. + /// Path to the host page within the . /// The . internal WebView2WebViewManager( WebView2Control webview!!, @@ -143,10 +148,11 @@ internal WebView2WebViewManager( Dispatcher dispatcher, IFileProvider fileProvider, JSComponentConfigurationStore jsComponents, - string hostPageRelativePath, + string contentRootRelativeToAppRoot, + string hostPagePathWithinFileProvider, BlazorWebViewHandler blazorWebViewHandler ) - : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath) + : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPagePathWithinFileProvider) { if (services.GetService() is null) { @@ -157,6 +163,7 @@ BlazorWebViewHandler blazorWebViewHandler _webview = webview; _blazorWebViewHandler = blazorWebViewHandler; + _contentRootRelativeToAppRoot = contentRootRelativeToAppRoot; // Unfortunately the CoreWebView2 can only be instantiated asynchronously. // We want the external API to behave as if initalization is synchronous, @@ -271,7 +278,7 @@ protected virtual Task HandleWebResourceRequest(CoreWebView2WebResourceRequested if (TryGetResponseContent(requestUri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)) { - StaticContentHotReloadManager.TryReplaceResponseContent(requestUri, ref statusCode, ref content, headers); + StaticContentHotReloadManager.TryReplaceResponseContent(_contentRootRelativeToAppRoot, requestUri, ref statusCode, ref content, headers); var headerString = GetHeaderString(headers); eventArgs.Response = _coreWebView2Environment!.CreateWebResourceResponse(content, statusCode, statusMessage, headerString); diff --git a/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs b/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs index 1eddebe07aa9..2aca39ddeb8a 100644 --- a/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs +++ b/src/BlazorWebView/src/WindowsForms/BlazorWebView.cs @@ -169,6 +169,7 @@ private void StartWebViewCoreIfPossible() } var hostPageFullPath = Path.GetFullPath(Path.Combine(appRootDir, HostPage!)); // HostPage is nonnull because RequiredStartupPropertiesSet is checked above var contentRootDirFullPath = Path.GetDirectoryName(hostPageFullPath)!; + var contentRootRelativePath = Path.GetRelativePath(appRootDir, contentRootDirFullPath); var hostPageRelativePath = Path.GetRelativePath(contentRootDirFullPath, hostPageFullPath); var fileProvider = CreateFileProvider(contentRootDirFullPath); @@ -179,12 +180,13 @@ private void StartWebViewCoreIfPossible() ComponentsDispatcher, fileProvider, RootComponents.JSComponents, + contentRootRelativePath, hostPageRelativePath, (args) => UrlLoading?.Invoke(this, args), (args) => BlazorWebViewInitializing?.Invoke(this, args), (args) => BlazorWebViewInitialized?.Invoke(this, args)); - StaticContentHotReloadManager.AttachToWebViewManagerIfEnabled(_webviewManager, WebView2WebViewManager.AppOrigin); + StaticContentHotReloadManager.AttachToWebViewManagerIfEnabled(_webviewManager, WebView2WebViewManager.AppOrigin, contentRootRelativePath); foreach (var rootComponent in RootComponents) { diff --git a/src/BlazorWebView/src/Wpf/BlazorWebView.cs b/src/BlazorWebView/src/Wpf/BlazorWebView.cs index 710a29fd85ff..bb9f8928d3e9 100644 --- a/src/BlazorWebView/src/Wpf/BlazorWebView.cs +++ b/src/BlazorWebView/src/Wpf/BlazorWebView.cs @@ -220,6 +220,7 @@ private void StartWebViewCoreIfPossible() var hostPageFullPath = Path.GetFullPath(Path.Combine(appRootDir, HostPage)); var contentRootDirFullPath = Path.GetDirectoryName(hostPageFullPath)!; var hostPageRelativePath = Path.GetRelativePath(contentRootDirFullPath, hostPageFullPath); + var contentRootDirRelativePath = Path.GetRelativePath(appRootDir, contentRootDirFullPath); var fileProvider = CreateFileProvider(contentRootDirFullPath); @@ -229,12 +230,13 @@ private void StartWebViewCoreIfPossible() ComponentsDispatcher, fileProvider, RootComponents.JSComponents, + contentRootDirRelativePath, hostPageRelativePath, (args) => UrlLoading?.Invoke(this, args), (args) => BlazorWebViewInitializing?.Invoke(this, args), (args) => BlazorWebViewInitialized?.Invoke(this, args)); - StaticContentHotReloadManager.AttachToWebViewManagerIfEnabled(_webviewManager, WebView2WebViewManager.AppOrigin); + StaticContentHotReloadManager.AttachToWebViewManagerIfEnabled(_webviewManager, WebView2WebViewManager.AppOrigin, contentRootDirRelativePath); foreach (var rootComponent in RootComponents) {