在我的.NET 8 Blazor项目中,我添加了“个人帐户”来管理用户。该应用程序有 2 个项目。
服务器端包含登录部分和控制器/端点。
控制器/端点是像这样的最小 API
public static void MapClientEndpoints (this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/api/Client").WithTags(nameof(Client));
group.MapGet("/", async (HypnoContext db) =>
{
return await db.Client.ToListAsync();
})
.RequireAuthorization()
.WithName("GetAllClients")
.WithOpenApi();
}
以便保存在数据库中。另外,我添加了
HttpClient
的配置:
builder.Services.AddScoped<AuthenticationStateProvider,
PersistingRevalidatingAuthenticationStateProvider>();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies();
builder.Services.AddIdentityCore<ApplicationUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
builder.Services
.AddScoped(sp => sp
.GetRequiredService<IHttpClientFactory>()
.CreateClient("ServerAPI"))
.AddHttpClient("ServerAPI", (provider, client) =>
{
client.BaseAddress = new Uri(builder. Configuration["FrontendUrl"]);
});
客户项目拥有所有观点。视图调用服务器提供的 API。
在 Program.cs 中,此配置是开箱即用的:
builder.Services.AddSingleton<AuthenticationStateProvider,
PersistentAuthenticationStateProvider>();
为了从页面连接到 API,我创建了一个类来将所有调用分组到一个位置。构造函数是这样的
string baseEndpoint;
IHttpClientFactory ClientFactory;
HttpClient? httpClient;
public ApiService(string baseUrl, IHttpClientFactory clientFactory)
{
baseEndpoint = baseUrl;
ClientFactory = clientFactory;
httpClient = ClientFactory.CreateClient("ServerAPI");
}
虽然我以用户身份登录应用程序,但只有在最小 API 中不需要授权的情况下,对 API 的调用才有效。就像
IHttpClientFactory
没有
如何保护 API 并从客户端使用它们?
在服务器的
Program.cs
中,我为HttpClient
添加了以下代码:
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient("ServerAPI", client => {
client.BaseAddress = new Uri(builder.Configuration["FrontendUrl"]);
})
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddTransient(sp =>
sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));
CustomAuthorizationMessageHandler
如下
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "https://localhost:7241" });
}
}
我这样做是因为我想调用受个人帐户保护的服务器项目中公开的 API。所以,在一个页面中
@inject HttpClient httpClient
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Get, "/api/Client");
await httpClient.SendAsync(request);
当我运行应用程序时,我立即收到错误:
System.AggregateException:“某些服务无法 构造(验证服务描述符时出错 '服务类型:MyApp.CustomAuthorizationMessageHandler 生命周期:范围实施类型: MyApp.CustomAuthorizationMessageHandler':无法解析 服务类型 'Microsoft.AspNetCore.Components.WebAssembly.Authentication.IAccessTokenProvider' 尝试激活时 'MyApp.CustomAuthorizationMessageHandler'。)'
请按照我的步骤检查您的项目:
1.controllers 文件夹包含静态类,如下所示
public static class ClientEndpint
{
public static void MapClientEndpoints(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/api/Client");
group.MapGet("/", async (ApplicationDbContext db) =>
{
return await db.Users.ToListAsync();
})
.RequireAuthorization()
.WithName("GetAllClients").WithOpenApi();
}
}
2.程序.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, PersistingRevalidatingAuthenticationStateProvider>();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies();
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
builder.Services
.AddScoped(sp => sp
.GetRequiredService<IHttpClientFactory>()
.CreateClient("ServerAPI"))
.AddHttpClient("ServerAPI", (provider, client) =>
{
client.BaseAddress = new Uri("https://localhost:7216");
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(BlazorApp1.Client._Imports).Assembly);
app.MapAdditionalIdentityEndpoints();
ClientEndpint.MapClientEndpoints(app); //be sure register the api
app.Run();
1.在razor组件中调用HttpClient(为了方便测试,我使用了
Counter.razor
)
@page "/counter"
@inject HttpClient httpClient
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
currentCount++;
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Get, "/api/Client");
await httpClient.SendAsync(request);
}
}
2.Program.cs(还需要在
HypnoPlatform.Client
项目中注册Httpclient)
注意:不要像
builder. Configuration["FrontendUrl"]
项目那样使用HypnoPlatform
,因为HypnoPlatform.Client
项目中没有appsettings.json。
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddSingleton<AuthenticationStateProvider, PersistentAuthenticationStateProvider>();
builder.Services
.AddScoped(sp => sp
.GetRequiredService<IHttpClientFactory>()
.CreateClient("ServerAPI"))
.AddHttpClient("ServerAPI", (provider, client) =>
{
client.BaseAddress = new Uri("https://localhost:7216");
});
await builder.Build().RunAsync();