我有一个Blazor WASM托管解决方案(服务器、客户端、模型(。显然,客户端是发布到服务器API控制器的blazor组件。然而,我希望有一些路由以MVC格式专门在服务器上运行。例如配置文件管理,如更改密码或升级会员选项。最安全的东西在服务器上提供,而不是托管在客户端。然而,我无法使这项工作发挥作用。我假设在服务器应用程序上设置MVC路由时我遗漏了一些东西。
这是我的服务器启动.cs
public void ConfigureServices(IServiceCollection services)
{
//Register the Datacontext and Connection String
services.AddDbContext<DataContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
//Sets up the default Asp.net core Identity Screens - Use Identity Scaffolding to override defaults
services.AddDefaultIdentity<ApplicationUser>( options =>
{
options.SignIn.RequireConfirmedAccount = true;
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequiredUniqueChars = 0;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredLength = 8;
options.User.RequireUniqueEmail = true;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<DataContext>();
//Associates the User to Context with Identity
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, DataContext>( options =>
{
options.IdentityResources["openid"].UserClaims.Add(JwtClaimTypes.Role);
options.ApiResources.Single().UserClaims.Add(JwtClaimTypes.Role);
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove(JwtClaimTypes.Role);
//Adds authentication handler
services.AddAuthentication().AddIdentityServerJwt();
services.AddHttpContextAccessor();
//Register Radzen Services
services.AddScoped<DialogService>();
services.AddScoped<NotificationService>();
services.AddScoped<TooltipService>();
services.AddScoped<ContextMenuService>();
services.AddControllersWithViews();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext dataContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
//AutoMigrates data
dataContext.Database.Migrate();
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseSerilogIngestion();
app.UseSerilogRequestLogging();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapFallbackToFile("index.html");
});
}
这是我试图在服务器上点击的控制器
[ApiController]
[Route("[controller]")]
public class ProfileController : Controller
{
private UserManager<ApplicationUser> _userManager;
private ILogger<ProfileController> _logger;
private readonly SignInManager<ApplicationUser> _signInManager;
public ProfileController(ILogger<ProfileController> logger, UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_logger = logger;
_signInManager = signInManager;
}
public IActionResult Index()
{
return View();
}
[HttpGet]
[Route("ChangePassword")]
public IActionResult ChangePassword()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
[Route("ChangePassword/{Id?}")]
public async Task<ActionResult> ChangePassword(ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
string userId = "";
if (User.Identity.IsAuthenticated)
userId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
var user = await _userManager.FindByIdAsync(userId);
var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return Redirect("/");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(error.Code, error.Description);
}
return View(model);
}
}
我可以通过我的客户端组件中的以下NavLink导航到ChangePassword页面
<NavLink class="btn btn-outline-primary" href="Profile/ChangePassword">Change password</NavLink>
然而,当我提交表格时,我得到了以下错误
{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.13","title":"Unsupported Media Type","status":415,"traceId":"00-4d07a26df9047d4bb56223728e7e2148-923c11953b1a754e-00"}
这是正在张贴的表格
@using (Html.BeginForm("ChangePassword", "Profile", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>Change Password Form</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.OldPassword, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.OldPassword, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.OldPassword, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.NewPassword, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.NewPassword, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.NewPassword, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.ConfirmPassword, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Change password" class="btn btn-primary white" />
</div>
</div>
}
所以这对我来说意味着我没有正确的配置,只是幸运的是,我点击了更改密码页面。
更新:我在Post方法中添加了FromForm,我不应该添加它,但这让它识别了Post类型。
[HttpPost]
[ValidateAntiForgeryToken]
[Route("ChangePassword/{Id?}")]
public async Task<ActionResult> ChangePassword([FromForm] ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
string userId = "";
if (User.Identity.IsAuthenticated)
userId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
var user = await _userManager.FindByIdAsync(userId);
var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return Redirect("/");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(error.Code, error.Description);
}
return View(model);
}
但是,如果我输入了无效的密码长度,当验证控件应该显示警告时,它们不会。如果我提交,那么我会在网络浏览器中收到另一条错误消息
{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"00-8376a8e3bd02c7438e45da7337b889ce-0d0e067ffd75a944-00","errors":{"NewPassword":["The New password must be at least 6 characters long."]}}
所以MVC方面的东西肯定不能正常工作
我认为Blazor试图使用current的上下文来访问Profile/ChangePassword
,但失败了。您需要退出Blazor的上下文(比如在URL栏中粘贴链接(,这样服务器就可以将请求路由到MVC框架,而不是Blazor框架。
将您的NavLink
更改为:
<NavLink class="btn btn-outline-primary" href="javascript:;" @onclick="(() => OnChangePasswordClick)">Change password</NavLink>
然后为OnChangePasswordClick
处理程序编写此代码。
@inject NavigationManager Navigation;
void OnChangePasswordClick()
{
Navigation.NavigateTo("Profile/ChangePassword", true)
}
NavigateTo
的第二个论点告诉Blazor强制重新加载浏览器。