Blazor: 为类型 Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView 找不到默认构造函数

6

我不明白为什么会一直出现这个异常。我试图保护一个 Blazor WebAssembly。

blazor.webassembly.js:1 WASM: System.MissingMethodException: 找不到类型 Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView 的默认构造函数。

app.razor

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <CascadingAuthenticationState>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </CascadingAuthenticationState>
    </NotFound>
</Router>

这是客户端程序.cs。
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");
            builder.Services.AddScoped<AuthenticationStateProvider, ApiAuthenticationStateProvider>();
            builder.Services.AddAuthorizationCore(options => { });            
            await builder.Build().RunAsync();
        }
    }

自定义身份验证状态提供程序
public class ApiAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly HttpClient _httpClient;
    private readonly ILocalStorageService _localStorage;

    public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
    {
        _httpClient = httpClient;
        _localStorage = localStorage;
    }
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var savedToken = await _localStorage.GetItemAsync<string>("authToken");

        if (string.IsNullOrWhiteSpace(savedToken))
        {
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        }

        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);

        return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
    }

    public void MarkUserAsAuthenticated(string email)
    {
        ClaimsPrincipal authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
        var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
        NotifyAuthenticationStateChanged(authState);
    }

    public void MarkUserAsLoggedOut()
    {
        var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
        var authState = Task.FromResult(new AuthenticationState(anonymousUser));
        NotifyAuthenticationStateChanged(authState);
    }

    private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
    {
        var claims = new List<Claim>();
        var payload = jwt.Split('.')[1];
        var jsonBytes = ParseBase64WithoutPadding(payload);
        var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);

        keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);

        if (roles != null)
        {
            if (roles.ToString().Trim().StartsWith("["))
            {
                var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());

                foreach (var parsedRole in parsedRoles)
                {
                    claims.Add(new Claim(ClaimTypes.Role, parsedRole));
                }
            }
            else
            {
                claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
            }

            keyValuePairs.Remove(ClaimTypes.Role);
        }

        claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));

        return claims;
    }

    private byte[] ParseBase64WithoutPadding(string base64)
    {
        switch (base64.Length % 4)
        {
            case 2: base64 += "=="; break;
            case 3: base64 += "="; break;
        }
        return Convert.FromBase64String(base64);
    }
}

服务器启动文件startup.cs

  public class Startup
    {
        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        ValidIssuer = Configuration["JwtIssuer"],
                        ValidAudience = Configuration["JwtAudience"],
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"]))
                    };
                });

            services.AddBlazoredLocalStorage();
            services.AddAuthorizationCore();
            services.AddScoped<AuthenticationStateProvider, ApiAuthenticationStateProvider>();
            services.AddScoped<IAuthService, AuthService>();

            services.AddMvc();
            services.AddResponseCompression(opts =>
            {
                opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
                    new[] { "application/octet-stream" });
            });


            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            services.AddDefaultIdentity<IdentityUser>()
                .AddEntityFrameworkStores<ApplicationDbContext>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseResponseCompression();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBlazorDebugging();
            }

            app.UseStaticFiles();
            app.UseClientSideBlazorFiles<Client.Program>();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
                endpoints.MapFallbackToClientSideBlazor<Client.Program>("index.html");
            });
        }
    }
4个回答

3
在 App.razor 文件顶部添加 @using Microsoft.AspNetCore.Components.Authorization
在 Program.Main(客户端)中添加选项和授权服务。
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

注意:以下内容(客户端):
 services.AddBlazoredLocalStorage();
 services.AddAuthorizationCore();
 services.AddScoped<AuthenticationStateProvider, 
                          ApiAuthenticationStateProvider>();
        services.AddScoped<IAuthService, AuthService>();

该归属于客户端,而非服务器端,尽管最终配置是相同的...

注:以下内容(客户端):

services.AddScoped<AuthenticationStateProvider, 
                          ApiAuthenticationStateProvider>();

应该是(客户端):

services.AddScoped<ApiAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<ApiAuthenticationStateProvider>());

在Startup类中,您应该注意顺序。

更新:

禁用链接器似乎是有效的临时解决方法。这就是如何禁用链接器:<BlazorLinkOnBuild>false</BlazorLinkOnBuild>

通过禁用链接器,可以防止移除未使用或未引用的代码,这可能是错误的原因...而启用链接器则允许这样做。但是,以下代码:

builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

应该防止链接器剥离未被引用的逻辑。更好的方法是,在 Program 类中添加自定义的 AuthenticationStateProvider 实现:

 builder.Services.AddScoped<AuthenticationStateProvider, 
                          ApiAuthenticationStateProvider>(); 

你应该已经解决了这个问题。你是按照这种方式做的吗?

再次强调,禁用链接器只是一种临时解决方案。


1
也许你的问题与链接问题有关。尝试像这样关闭链接器:<BlazorLinkOnBuild>false</BlazorLinkOnBuild>。 - enet
这个有效了,我在客户端上添加了它,我的应用程序终于加载了。虽然我不知道为什么或者它与什么有关。 - Jackal
请在每个代码块中添加项目位置的客户端或服务器。 - Simon
我在自己创建的一个组件中遇到了问题...它是一个<Menu>组件。 - Luke T O'Brien

1
我曾经遇到过这个问题。当我从NuGet安装了Microsoft.AspNetCore.Components.Authorization之后,问题就解决了。在Visual Studio中,@using Microsoft.AspNetCore.Components.Authorization指令没有任何视觉提示表明该包已丢失。

这是唯一对我有效的解决方案。谢谢! - Sjoerdson

1
添加
builder.Services.AddApiAuthorization();

安装

Microsoft.AspNetCore.Components.WebAssembly.Authentication

my main method:

    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("app");
        builder.Services.AddOptions();
        builder.Services.AddAuthorizationCore();
        builder.Services.AddScoped<CustomStateProvider>();
        builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomStateProvider>());
        builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
        builder.Services.AddApiAuthorization();

        await builder.Build().RunAsync();
    }

0

目前似乎禁用链接器是唯一可行的解决方案。

我添加了以下代码:

builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

尝试添加自定义的AuthenticationStateProvider,但没有成功。

Github上的问题似乎已通过此解决方法关闭:https://github.com/dotnet/aspnetcore/issues/22298


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接