前言
在MVC中我們經常使用內置的Identity來實現登錄,簡單快捷。當然,還有其他方式來實現,但是在blazor中使用 SignInAsync會出現這樣的錯誤 "The response headers cannot be modified because the response has already started"。
我通過依賴注入IHttpContextAccessor _httpContextAccessor,來獲取HttpContext調用SignInAsync方法。blazor是SignaIR 長輪詢來實現的,所以會出現這樣的錯誤。我們需要創建一個新的請求來實現。
實現
ASP.NET Core 項目是可以同時托管多種不同的程序的。例如,在一個項目能運行Webapi和Blazor(注意,我指的並不是在一個解決方案),我們需要創建一個控制器,添加登錄方法。
//AccountController.cs
public class AccountController :ControllerBase
{
private readonly IDbContextFactory<BlogContext> _dbFactory;
private readonly IDataProtectionProvider _dataProtectionProvider;
public AccountController(IDataProtectionProvider dataProtectionProvider, IDbContextFactory<BlogContext> dbFactory)
{
_dbFactory = dbFactory;
_dataProtectionProvider = dataProtectionProvider;
}
/// <summary>
/// 后端登錄
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet]
[Route("Login")]
//[AuthorizationVerify]
[Authorize(Policy = "AtLeast21")]
public async Task<IActionResult> Login(string token)
{
var dataProtect = _dataProtectionProvider.CreateProtector("Login");
var data = dataProtect.Unprotect(token);
var parts = data.Split('|');
using var context = _dbFactory.CreateDbContext();
var user = await context.Admin.FirstOrDefaultAsync(x => !x.IsDelete && x.Uno.Equals(parts[0]) && x.PassWord.Equals(parts[1]));
if (user != null)
{
#region 用戶信息憑證
AuthenticationProperties props = null;
var claims = new List<Claim>() {
new Claim(ClaimTypes.Sid, user.Uno),
new Claim(ClaimTypes.Name,user.UserName),
new Claim(ClaimTypes.Uri, user.ImageUrl),
};
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromDays(1))
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme)),
props);
#endregion
return Redirect("/Admin/index");
}
else
{
return Redirect($"/Login/{true}");
}
}
}
上面,我們創建了一個控制器,添加了登錄驗證方法。但想要同時運行Webapi和Blazor,還需要對Startup.cs文件 Configure方法終結點進行修改。
// Startup.cs
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
endpoints.MapControllers();
// 新添加的控制器終結點
// 項目會按照終結點的順序來進行訪問
// 先走上面的Blazor,如果找不對應的路由信息
// 會走到webapi的控制器路由來查找
});
接下來,就是Login.Razor組件的部分了。這里我們需要通過加密處理賬號和密碼,通過_navigationManager來實現調用。這里不能使用httpclient的方式,應該是請求會被判斷為服務器發出,在瀏覽器內的NetWork中沒有記錄。(只是猜測,也可能是長輪詢的問題)
//Login.Razor.cs
//賬號密碼加密處理
//需要注入IDataProtectionProvider _dataProtectionProvider
var dataProtect = _dataProtectionProvider.CreateProtector("Login");
var input = dataProtect.Protect($"{model.Username}|{model.Password}");
//需要在組件中注入NavigationManager _navigationManager
_navigationManager.NavigateTo("/api/Account/Login?token=" + input, true);
控制器中的Login方法通過Redirect重定向回來,或去要訪問的頁面就可以了。

這樣就成功實現登錄功能了。
