diff --git a/src/BlazorWebView/samples/BlazorWinFormsApp/BlazorWinFormsApp.csproj b/src/BlazorWebView/samples/BlazorWinFormsApp/BlazorWinFormsApp.csproj index 91c9e0e219aa..b0cfa65c22fa 100644 --- a/src/BlazorWebView/samples/BlazorWinFormsApp/BlazorWinFormsApp.csproj +++ b/src/BlazorWebView/samples/BlazorWinFormsApp/BlazorWinFormsApp.csproj @@ -1,5 +1,7 @@ + + net6.0-windows 7.0 @@ -19,4 +21,6 @@ + + diff --git a/src/BlazorWebView/samples/BlazorWpfApp/BlazorWpfApp.csproj b/src/BlazorWebView/samples/BlazorWpfApp/BlazorWpfApp.csproj index 97db353c6e49..13e9248e517a 100644 --- a/src/BlazorWebView/samples/BlazorWpfApp/BlazorWpfApp.csproj +++ b/src/BlazorWebView/samples/BlazorWpfApp/BlazorWpfApp.csproj @@ -1,5 +1,7 @@ + + net6.0-windows 7.0 @@ -19,4 +21,6 @@ + + diff --git a/src/BlazorWebView/samples/MauiRazorClassLibrarySample/MauiRazorClassLibrarySample.csproj b/src/BlazorWebView/samples/MauiRazorClassLibrarySample/MauiRazorClassLibrarySample.csproj index d4e53c2f4e87..dd59f227b2e4 100644 --- a/src/BlazorWebView/samples/MauiRazorClassLibrarySample/MauiRazorClassLibrarySample.csproj +++ b/src/BlazorWebView/samples/MauiRazorClassLibrarySample/MauiRazorClassLibrarySample.csproj @@ -3,7 +3,6 @@ net6.0 false - false diff --git a/src/BlazorWebView/samples/WebViewAppShared/WebViewAppShared.csproj b/src/BlazorWebView/samples/WebViewAppShared/WebViewAppShared.csproj index b61a5dad0b2b..f74cf97210f3 100644 --- a/src/BlazorWebView/samples/WebViewAppShared/WebViewAppShared.csproj +++ b/src/BlazorWebView/samples/WebViewAppShared/WebViewAppShared.csproj @@ -3,7 +3,6 @@ net6.0 false - false diff --git a/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj b/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj index 1828814933a8..a45718aa7e0e 100644 --- a/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj +++ b/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj @@ -5,7 +5,6 @@ 9.0 enable Build .NET MAUI applications with Blazor web UI in the BlazorWebView control. - $(DefineConstants);MAUI_INCLUDE diff --git a/src/BlazorWebView/src/Maui/build/Microsoft.AspNetCore.Components.WebView.Maui.targets b/src/BlazorWebView/src/Maui/build/Microsoft.AspNetCore.Components.WebView.Maui.targets index dee497a8e8fd..e0e19e95e30d 100644 --- a/src/BlazorWebView/src/Maui/build/Microsoft.AspNetCore.Components.WebView.Maui.targets +++ b/src/BlazorWebView/src/Maui/build/Microsoft.AspNetCore.Components.WebView.Maui.targets @@ -48,21 +48,4 @@ - - - - - - <_MauiManifestsToUpdate Include="@(StaticWebAssetManifest)" Condition="'%(ManifestType)' == 'Publish'" /> - <_MauiManifestsToUpdate> - @(_MauiManifestsToUpdate->'%(AdditionalPublishPropertiesToRemove)');TargetFramework - - - - - - - diff --git a/src/BlazorWebView/src/WebView2/Internal/ManifestStaticWebAssetsFileProvider.cs b/src/BlazorWebView/src/WebView2/Internal/ManifestStaticWebAssetsFileProvider.cs deleted file mode 100644 index 3ea9efd76b80..000000000000 --- a/src/BlazorWebView/src/WebView2/Internal/ManifestStaticWebAssetsFileProvider.cs +++ /dev/null @@ -1,391 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text.Json; -using System.Text.Json.Serialization; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.FileSystemGlobbing; -using Microsoft.Extensions.Primitives; - -namespace Microsoft.AspNetCore.Components.WebView.WebView2.Tmp -{ - internal sealed class ManifestStaticWebAssetFileProvider : IFileProvider - { - private static readonly StringComparison _fsComparison = OperatingSystem.IsWindows() ? - StringComparison.OrdinalIgnoreCase : - StringComparison.Ordinal; - - private static readonly IEqualityComparer _nameComparer = new FileNameComparer(); - - private readonly IFileProvider[] _fileProviders; - private readonly StaticWebAssetNode _root; - - public ManifestStaticWebAssetFileProvider(StaticWebAssetManifest manifest, Func fileProviderFactory) - { - _fileProviders = new IFileProvider[manifest.ContentRoots.Length]; - - - for (int i = 0; i < manifest.ContentRoots.Length; i++) - { - _fileProviders[i] = fileProviderFactory(manifest.ContentRoots[i]); - } - - _root = manifest.Root; - } - - public IDirectoryContents GetDirectoryContents(string subpath) - { - if (subpath == null) - { - throw new ArgumentNullException(nameof(subpath)); - } - - var segments = Normalize(subpath).Split('/', StringSplitOptions.RemoveEmptyEntries); - var candidate = _root; - - // Iterate over the path segments until we reach the destination. Whenever we encounter - // a pattern, we start tracking it as well as the content root directory. On every segment - // we evalutate the directory to see if there is a subfolder with the current segment and - // replace it on the dictionary if it exists or remove it if it does not. - // When we reach our destination we enumerate the files in that folder and evalute them against - // the pattern to compute the final list. - HashSet files = null; - for (var i = 0; i < segments.Length; i++) - { - files = GetFilesForCandidatePatterns(segments, candidate, files); - - - if (candidate.HasChildren() && candidate.Children.TryGetValue(segments[i], out var child)) - { - candidate = child; - } - else - { - candidate = null; - break; - } - } - - if ((candidate == null || (!candidate.HasChildren() && !candidate.HasPatterns())) && files == null) - { - return NotFoundDirectoryContents.Singleton; - } - else - { - // We do this to make sure we account for the patterns on the last segment which are not covered by the loop above - files = GetFilesForCandidatePatterns(segments, candidate, files); - if (candidate != null && candidate.HasChildren()) - { - files ??= new(_nameComparer); - GetCandidateFilesForNode(candidate, files); - } - - return new StaticWebAssetsDirectoryContents((files as IEnumerable) ?? Array.Empty()); - } - - HashSet GetFilesForCandidatePatterns(string[] segments, StaticWebAssetNode candidate, HashSet files) - { - if (candidate != null && candidate.HasPatterns()) - { - var depth = candidate.Patterns[0].Depth; - var candidateDirectoryPath = string.Join('/', segments[depth..]); - foreach (var pattern in candidate.Patterns) - { - var contentRoot = _fileProviders[pattern.ContentRoot]; - var matcher = new Matcher(_fsComparison); - matcher.AddInclude(pattern.Pattern); - foreach (var result in contentRoot.GetDirectoryContents(candidateDirectoryPath)) - { - var fileCandidate = string.IsNullOrEmpty(candidateDirectoryPath) ? result.Name : $"{candidateDirectoryPath}/{result.Name}"; - if (result.Exists && (result.IsDirectory || matcher.Match(fileCandidate).HasMatches)) - { - files ??= new(_nameComparer); - if (!files.Contains(result)) - { - // Multiple patterns might match the same file (even at different locations on disk) at runtime. We don't - // try to disambiguate anything here, since there is already a build step for it. We just pick the first - // file that matches the pattern. The manifest entries are ordered, so while this choice is random, it is - // nonetheless deterministic. - files.Add(result); - } - } - } - } - } - - - return files; - } - - void GetCandidateFilesForNode(StaticWebAssetNode candidate, HashSet files) - { - foreach (var child in candidate.Children!) - { - var match = child.Value.Match; - if (match == null) - { - // This is a folder - var file = new StaticWebAssetsDirectoryInfo(child.Key); - // Entries from the manifest always win over any content based on patterns, - // so remove any potentially existing file or folder in favor of the manifest - // entry. - files.Remove(file); - files.Add(file); - } - else - { - // This is a file. - files.RemoveWhere(f => string.Equals(match.Path, f.Name, _fsComparison)); - var file = _fileProviders[match.ContentRoot].GetFileInfo(match.Path); - - - files.Add(string.Equals(child.Key, match.Path, _fsComparison) ? file : - // This means that this file was mapped, there is a chance that we added it to the list - // of files by one of the patterns, so we need to replace it with the mapped file. - new StaticWebAssetsFileInfo(child.Key, file)); - } - } - } - } - - private string Normalize(string path) - { - return path.Replace('\\', '/'); - } - - public IFileInfo GetFileInfo(string subpath) - { - if (subpath == null) - { - throw new ArgumentNullException(nameof(subpath)); - } - - var segments = subpath.Split('/', StringSplitOptions.RemoveEmptyEntries); - StaticWebAssetNode candidate = _root; - List patterns = null; - - // Iterate over the path segments until we reach the destination, collecting - // all pattern candidates along the way except for any pattern at the root. - for (var i = 0; i < segments.Length; i++) - { - if (candidate.HasPatterns()) - { - patterns ??= new(); - patterns.AddRange(candidate.Patterns); - } - if (candidate.HasChildren() && candidate.Children.TryGetValue(segments[i], out var child)) - { - candidate = child; - } - else - { - candidate = null; - break; - } - } - - var match = candidate?.Match; - if (match != null) - { - // If we found a file, that wins over anything else. If there are conflicts with files added after - // we've built the manifest, we'll be notified the next time we do a build. This is not different - // from previous Static Web Assets versions. - var file = _fileProviders[match.ContentRoot].GetFileInfo(match.Path); - if (!file.Exists || string.Equals(subpath, Normalize(match.Path), _fsComparison)) - { - return file; - } - else - { - return new StaticWebAssetsFileInfo(segments[^1], file); - } - } - - // The list of patterns is ordered by pattern depth, so we compute the string to check for patterns only - // once per level. We don't aim to solve conflicts here where multiple files could match a given path, - // we have a build check that takes care of that. - var currentDepth = 0; - var candidatePath = subpath; - - if (patterns != null) - { - for (var i = 0; i < patterns.Count; i++) - { - var pattern = patterns[i]; - if (pattern.Depth != currentDepth) - { - currentDepth = pattern.Depth; - candidatePath = string.Join('/', segments[currentDepth..]); - } - - - var result = _fileProviders[pattern.ContentRoot].GetFileInfo(candidatePath); - if (result.Exists) - { - if (!result.IsDirectory) - { - var matcher = new Matcher(); - matcher.AddInclude(pattern.Pattern); - if (!matcher.Match(candidatePath).HasMatches) - { - continue; - } - - - return result; - } - } - } - } - - return new NotFoundFileInfo(subpath); - } - - public IChangeToken Watch(string filter) => NullChangeToken.Singleton; - - private sealed class StaticWebAssetsDirectoryContents : IDirectoryContents - { - private readonly IEnumerable _files; - - public StaticWebAssetsDirectoryContents(IEnumerable files) => - _files = files; - - public bool Exists => true; - - public IEnumerator GetEnumerator() => _files.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - private sealed class StaticWebAssetsDirectoryInfo : IFileInfo - { - private static readonly DateTimeOffset _lastModified = DateTimeOffset.FromUnixTimeSeconds(0); - - - public StaticWebAssetsDirectoryInfo(string name) - { - Name = name; - } - - - public bool Exists => true; - - public long Length => 0; - - public string PhysicalPath => null; - - public DateTimeOffset LastModified => _lastModified; - - public bool IsDirectory => true; - - public string Name { get; } - - public Stream CreateReadStream() => throw new InvalidOperationException("Can not create a stream for a directory."); - } - - private sealed class StaticWebAssetsFileInfo : IFileInfo - { - private readonly IFileInfo _source; - - public StaticWebAssetsFileInfo(string name, IFileInfo source) - { - Name = name; - _source = source; - } - - public bool Exists => _source.Exists; - - public long Length => _source.Length; - - public string PhysicalPath => _source.PhysicalPath; - - public DateTimeOffset LastModified => _source.LastModified; - - public bool IsDirectory => _source.IsDirectory; - - public string Name { get; } - - public Stream CreateReadStream() => _source.CreateReadStream(); - } - - - private sealed class FileNameComparer : IEqualityComparer - { - public bool Equals(IFileInfo x, IFileInfo y) => string.Equals(x?.Name, y?.Name, _fsComparison); - - public int GetHashCode(IFileInfo obj) => obj.Name.GetHashCode(_fsComparison); - } - - - internal sealed class StaticWebAssetManifest - { - internal static readonly StringComparer PathComparer = - OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - - public string[] ContentRoots { get; set; } = Array.Empty(); - - public StaticWebAssetNode Root { get; set; } = null!; - - internal static StaticWebAssetManifest Parse(Stream manifest) - { - return JsonSerializer.Deserialize(manifest)!; - } - } - - internal sealed class StaticWebAssetNode - { - [JsonPropertyName("Asset")] - public StaticWebAssetMatch Match { get; set; } - - [JsonConverter(typeof(OSBasedCaseConverter))] - public Dictionary Children { get; set; } - - public StaticWebAssetPattern[] Patterns { get; set; } - - [MemberNotNullWhen(true, nameof(Children))] - internal bool HasChildren() => Children != null && Children.Count > 0; - - [MemberNotNullWhen(true, nameof(Patterns))] - internal bool HasPatterns() => Patterns != null && Patterns.Length > 0; - } - - internal sealed class StaticWebAssetMatch - { - [JsonPropertyName("ContentRootIndex")] - public int ContentRoot { get; set; } - - [JsonPropertyName("SubPath")] - public string Path { get; set; } = null!; - } - - internal sealed class StaticWebAssetPattern - { - [JsonPropertyName("ContentRootIndex")] - public int ContentRoot { get; set; } - - public int Depth { get; set; } - - public string Pattern { get; set; } = null!; - } - - private sealed class OSBasedCaseConverter : JsonConverter> - { - public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new Dictionary( - JsonSerializer.Deserialize>(ref reader, options)!, - StaticWebAssetManifest.PathComparer); - } - - public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value, options); - } - } - } -} diff --git a/src/BlazorWebView/src/WebView2/Internal/StaticContentProvider.cs b/src/BlazorWebView/src/WebView2/Internal/StaticContentProvider.cs index cc4f6d530782..61245ff87e8a 100644 --- a/src/BlazorWebView/src/WebView2/Internal/StaticContentProvider.cs +++ b/src/BlazorWebView/src/WebView2/Internal/StaticContentProvider.cs @@ -10,9 +10,6 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; -#if !MAUI_INCLUDE -using Microsoft.AspNetCore.Components.WebView.WebView2.Tmp; -#endif using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Components.WebView.WebView2.Internal @@ -488,121 +485,4 @@ private static string GetExtension(string path) } } } - -#if !MAUI_INCLUDE - internal partial class StaticContentProvider - { - private readonly IFileProvider _fileProvider; - private readonly Uri _appBaseUri; - private readonly string _hostPageRelativePath; - private static readonly ManifestEmbeddedFileProvider _manifestProvider = - new ManifestEmbeddedFileProvider(typeof(WebViewManager).Assembly); - - public static StaticContentProvider ResolveFromStaticWebAssetsManifest(IFileProvider fileProvider, Uri uri, string hostPageRelativePath) - { - var assembly = Assembly.GetEntryAssembly(); - if (string.IsNullOrEmpty(assembly?.Location)) - { - return null; - } - - var name = Path.GetFileNameWithoutExtension(assembly.Location); - var manifestPath = Path.Combine(Path.GetDirectoryName(assembly.Location)!, $"{name}.staticwebassets.runtime.json"); - if (!File.Exists(manifestPath)) - { - return null; - } - - using var stream = File.OpenRead(Path.Combine(Path.GetDirectoryName(assembly.Location)!, $"{name}.staticwebassets.runtime.json")); - var manifest = ManifestStaticWebAssetFileProvider.StaticWebAssetManifest.Parse(stream); - var provider = new ManifestStaticWebAssetFileProvider(manifest, (p) => new PhysicalFileProvider(p)); - return new StaticContentProvider(provider, uri, hostPageRelativePath); - } - - public StaticContentProvider(IFileProvider fileProvider, Uri appBaseUri, string hostPageRelativePath) - { - _fileProvider = fileProvider ?? throw new ArgumentNullException(nameof(fileProvider)); - _appBaseUri = appBaseUri ?? throw new ArgumentNullException(nameof(appBaseUri)); - _hostPageRelativePath = hostPageRelativePath ?? throw new ArgumentNullException(nameof(hostPageRelativePath)); - } - - public bool TryGetResponseContent(string requestUri, bool allowFallbackOnHostPage, out int statusCode, out string statusMessage, out Stream content, out IDictionary headers) - { - var fileUri = new Uri(requestUri); - if (_appBaseUri.IsBaseOf(fileUri)) - { - var relativePath = _appBaseUri.MakeRelativeUri(fileUri).ToString(); - - // Content in the file provider takes first priority - // Next we may fall back on supplying the host page to support deep linking - // If there's no match, fall back on serving embedded framework content - string contentType; - var found = TryGetFromFileProvider(relativePath, out content, out contentType) - || (allowFallbackOnHostPage && TryGetFromFileProvider(_hostPageRelativePath, out content, out contentType)) - || TryGetFrameworkFile(relativePath, out content, out contentType); - - if (found) - { - statusCode = 200; - statusMessage = "OK"; - headers = GetResponseHeaders(contentType); - } - else - { - content = new MemoryStream(Encoding.UTF8.GetBytes($"There is no content at {relativePath}")); - statusCode = 404; - statusMessage = "Not found"; - headers = GetResponseHeaders("text/plain"); - } - - // Always respond to requests within the base URI, even if there's no matching file - return true; - } - else - { - // URL isn't within application base path, so let the network handle it - statusCode = default; - statusMessage = default; - headers = default; - content = default; - return false; - } - } - - private bool TryGetFromFileProvider(string relativePath, out Stream content, out string contentType) - { - if (!string.IsNullOrEmpty(relativePath)) - { - var fileInfo = _fileProvider.GetFileInfo(relativePath); - if (fileInfo.Exists) - { - content = fileInfo.CreateReadStream(); - contentType = GetResponseContentTypeOrDefault(fileInfo.Name); - return true; - } - } - - content = default; - contentType = default; - return false; - } - - private static bool TryGetFrameworkFile(string relativePath, out Stream content, out string contentType) - { - // We're not trying to simulate everything a real webserver does. We don't need to - // support querystring parameters, for example. It's enough to require an exact match. - var file = _manifestProvider.GetFileInfo(relativePath); - if (file.Exists) - { - content = file.CreateReadStream(); - contentType = GetResponseContentTypeOrDefault(relativePath); - return true; - } - - content = default; - contentType = default; - return false; - } - } -#endif } diff --git a/src/BlazorWebView/src/WebView2/WebView2WebViewManager.cs b/src/BlazorWebView/src/WebView2/WebView2WebViewManager.cs index 198ec4bd2da1..131285c0292f 100644 --- a/src/BlazorWebView/src/WebView2/WebView2WebViewManager.cs +++ b/src/BlazorWebView/src/WebView2/WebView2WebViewManager.cs @@ -7,8 +7,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebView.WebView2.Internal; -using Microsoft.AspNetCore.Components.WebView.WebView2.Tmp; using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Components.WebView.WebView2 @@ -25,7 +23,6 @@ public class WebView2WebViewManager : WebViewManager private const string AppOrigin = "https://0.0.0.0/"; private readonly IWebView2Wrapper _webview; - private readonly StaticContentProvider _staticContentProvider = null; private readonly Task _webviewReadyTask; /// @@ -40,7 +37,6 @@ public WebView2WebViewManager(IWebView2Wrapper webview, IServiceProvider service : base(services, dispatcher, new Uri(AppOrigin), fileProvider, jsComponents, hostPageRelativePath) { _webview = webview ?? throw new ArgumentNullException(nameof(webview)); - _staticContentProvider = StaticContentProvider.ResolveFromStaticWebAssetsManifest(fileProvider, new Uri(AppOrigin), hostPageRelativePath); // Unfortunately the CoreWebView2 can only be instantiated asynchronously. // We want the external API to behave as if initalization is synchronous, @@ -78,7 +74,7 @@ private async Task InitializeWebView2() eventArgs.ResourceContext == CoreWebView2WebResourceContextWrapper.Document || eventArgs.ResourceContext == CoreWebView2WebResourceContextWrapper.Other; // e.g., dev tools requesting page source - if (TryGetResponseContentNewManifest(eventArgs.Request.Uri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)) + if (TryGetResponseContent(eventArgs.Request.Uri, allowFallbackOnHostPage, out var statusCode, out var statusMessage, out var content, out var headers)) { var headerString = GetHeaderString(headers); eventArgs.SetResponse(content, statusCode, statusMessage, headerString); @@ -136,8 +132,5 @@ private void ApplyDefaultWebViewSettings() } }); } - - private bool TryGetResponseContentNewManifest(string uri, bool allowFallbackOnHostPage, out int statusCode, out string statusMessage, out Stream content, out IDictionary headers) => - _staticContentProvider.TryGetResponseContent(uri, allowFallbackOnHostPage, out statusCode, out statusMessage, out content, out headers); } } diff --git a/src/BlazorWebView/src/WindowsForms/Microsoft.AspNetCore.Components.WebView.WindowsForms.csproj b/src/BlazorWebView/src/WindowsForms/Microsoft.AspNetCore.Components.WebView.WindowsForms.csproj index 1262024b956f..2fbe68fe92f9 100644 --- a/src/BlazorWebView/src/WindowsForms/Microsoft.AspNetCore.Components.WebView.WindowsForms.csproj +++ b/src/BlazorWebView/src/WindowsForms/Microsoft.AspNetCore.Components.WebView.WindowsForms.csproj @@ -20,4 +20,8 @@ + + + + diff --git a/src/BlazorWebView/src/WindowsForms/build/Microsoft.AspNetCore.Components.WebView.WindowsForms.props b/src/BlazorWebView/src/WindowsForms/build/Microsoft.AspNetCore.Components.WebView.WindowsForms.props new file mode 100644 index 000000000000..7bc91e44385a --- /dev/null +++ b/src/BlazorWebView/src/WindowsForms/build/Microsoft.AspNetCore.Components.WebView.WindowsForms.props @@ -0,0 +1,2 @@ + + diff --git a/src/BlazorWebView/src/WindowsForms/build/Microsoft.AspNetCore.Components.WebView.WindowsForms.targets b/src/BlazorWebView/src/WindowsForms/build/Microsoft.AspNetCore.Components.WebView.WindowsForms.targets new file mode 100644 index 000000000000..3491a92f497e --- /dev/null +++ b/src/BlazorWebView/src/WindowsForms/build/Microsoft.AspNetCore.Components.WebView.WindowsForms.targets @@ -0,0 +1,18 @@ + + + + / + Root + $(CoreCompileDependsOn);StaticWebAssetsPrepareForRun + + + + + + + + + + + + \ No newline at end of file diff --git a/src/BlazorWebView/src/Wpf/Microsoft.AspNetCore.Components.WebView.Wpf.csproj b/src/BlazorWebView/src/Wpf/Microsoft.AspNetCore.Components.WebView.Wpf.csproj index 8a84bb321d41..1078253942aa 100644 --- a/src/BlazorWebView/src/Wpf/Microsoft.AspNetCore.Components.WebView.Wpf.csproj +++ b/src/BlazorWebView/src/Wpf/Microsoft.AspNetCore.Components.WebView.Wpf.csproj @@ -1,4 +1,4 @@ - +