dotnet-wasi-sdk
dotnet-wasi-sdk copied to clipboard
Wasi.AspNetCore.BundledFiles.WasiBundledFileProvider.Watch is not implemented
Description
I tried to use the Wasi.AspNetCore.BundledFiles package in order to bundle the static files of my web, and I got an error about a not implemented method.
Reproduction Steps
$ dotnet --version
7.0.100-rc.2.22477.23 (Linux x64)
$ dotnet new webapp -o MyWebApp
$ cd MyWebApp
$ dotnet add package Wasi.Sdk --prerelease
$ dotnet add package Wasi.AspNetCore.Server.Native --prerelease
$ dotnet add package Wasi.AspNetCore.BundledFiles --prerelease
And follow steps in How to use: ASP.NET Core applications for code modifications.
$ dotnet run -c Release
Expected behavior
Web app work!
Actual behavior
Building...
info: Microsoft.Hosting.Lifetime
Now listening on: http://localhost:8080
info: Microsoft.AspNetCore.Hosting.Diagnostics
Request starting HTTP/1.1 GET http://localhost:8080/ - -
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware
Failed to determine the https port for redirect.
info: Microsoft.AspNetCore.Routing.EndpointMiddleware
Executing endpoint '/Index'
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker
Route matched with {page = "/Index"}. Executing page /Index
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker
Executing handler method WASMWebApp.Pages.IndexModel.OnGet - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker
Executed handler method OnGet, returned result .
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker
Executing an implicit handler method - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker
Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
info: Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker
Executed page /Index in 239.386ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware
Executed endpoint '/Index'
erro: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware
An unhandled exception has occurred while executing the request.
System.NotImplementedException: The method or operation is not implemented.
at Wasi.AspNetCore.BundledFiles.WasiBundledFileProvider.Watch(String filter)
at Microsoft.AspNetCore.Mvc.Razor.Infrastructure.DefaultFileVersionProvider.AddFileVersionToPath(PathString requestPathBase, String path)
at Microsoft.AspNetCore.Mvc.TagHelpers.LinkTagHelper.Process(TagHelperContext context, TagHelperOutput output)
at Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.RunAsync(TagHelperExecutionContext executionContext)
at WASMWebApp.Pages.Shared.Pages_Shared__Layout.<ExecuteAsync>b__24_0()
at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
at WASMWebApp.Pages.Shared.Pages_Shared__Layout.ExecuteAsync()
at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderLayoutAsync(ViewContext context, ViewBufferTextWriter bodyWriter)
at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<<InvokeNextResultFilterAsync>g__Awaited|30_0>d`2[[Microsoft.AspNetCore.Mvc.Filters.IResultFilter, Microsoft.AspNetCore.Mvc.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter, Microsoft.AspNetCore.Mvc.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].MoveNext()
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[IResultFilter,IAsyncResultFilter](State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|8_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)
Configuration
Please provide more information on your .NET configuration:
Which version of .NET is the code running on? 7.0.100-rc.2.22477.23 What OS and version, and what distro if applicable? Ubuntu 22.04 LTS (WSL) What is the architecture (x64, x86, ARM, ARM64)? x64 WASM runner? wasmtime-cli 2.0.0
I found a simple workaround:
- Add the following class to your project
Do not rename the namespace or class otherwise the Mono interop will not work
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Wasi.AspNetCore.BundledFiles;
// Do NOT change the namespace or type name
// Wasi.AspNetCore.BundledFiles.WasiBundledFileProvider
// Otherwise Mono will not find the interop call (GetEmbeddedFile)
public class WasiBundledFileProvider : IFileProvider
{
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern unsafe byte* GetEmbeddedFile(string name, out int length);
private readonly static DateTime FakeLastModified = new DateTime(2000, 1, 1);
public IDirectoryContents GetDirectoryContents(string subpath)
{
return new BundledDirectoryContents(this, subpath);
}
public unsafe IFileInfo GetFileInfo(string subpath)
{
var subpathWithoutLeadingSlash = subpath.AsSpan(1);
var fileBytes = GetEmbeddedFile($"wwwroot/{subpathWithoutLeadingSlash}", out var length);
return fileBytes == null
? new NotFoundFileInfo(subpath)
: new BundledFileInfo(subpath, length, FakeLastModified, fileBytes);
}
public IChangeToken Watch(string filter)
{
return NullChangeToken.Singleton;
}
unsafe class BundledFileInfo : IFileInfo
{
private byte* _fileBytes;
public BundledFileInfo(string name, long length, DateTime lastModified, byte* fileBytes)
{
Name = name;
LastModified = lastModified;
Length = length;
_fileBytes = fileBytes;
}
public bool Exists => true;
public bool IsDirectory => false;
public DateTimeOffset LastModified { get; }
public long Length { get; }
public string Name { get; }
public string? PhysicalPath => null;
public Stream CreateReadStream()
=> new UnmanagedMemoryStream(_fileBytes, Length);
}
class BundledDirectoryContents : IDirectoryContents
{
private readonly IFileProvider _owner;
private readonly string _subpath;
public BundledDirectoryContents(IFileProvider owner, string subpath)
{
_owner = owner;
_subpath = subpath;
}
public bool Exists => _subpath == "/";
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public IEnumerator<IFileInfo> GetEnumerator()
{
// TODO: Mechanism for enumerating everything in a bundled directory
// Currently this only recognizes index.html files to support UseDefaultFiles
if (_subpath == "/")
{
var fileInfo = _owner.GetFileInfo("/index.html");
if (fileInfo.Exists)
{
yield return fileInfo;
}
}
}
}
}
This is obviously the culprit:
public IChangeToken Watch(string filter)
{
return NullChangeToken.Singleton;
}
- Configure the
StaticFileOptionseither usingUseStaticFilesorUseBundledStaticFiles:
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new WasiBundledFileProvider()
});