Asp.Net Core 3.0 MVC - 文化 Cookie 的脚手架身份页面路由
Asp.Net Core 3.0 MVC - Scaffolded Identity Page Routing for Culture Cookie
-
asp.net-mvc-routing
-
asp.net-mvc-scaffolding
-
asp.net-identity
-
asp.net-core-localization
-
lazziya.expresslocalization
我已经在我的 Asp.Net Core MVC 3.0 应用程序中实现了这个用于本地化的 nuget 包,我发现它很棒。
https://github.com/LazZiya/ExpressLocalization
除了我的脚手架身份页面之外的所有页面都工作正常。
当我导航到我的脚手架 Register.cshtml 页面时,我遇到了一个问题,我的本地化 cookie 没有被放入 URL。但是,如果我手动将文化(例如 de
或 en
等)键入 URL,则本地化工作正常。
例如,当我点击注册 link时,URL应该是
但它总是
Startup.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.EntityFrameworkCore;
using MyApp.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using LazZiya.ExpressLocalization;
using Microsoft.AspNetCore.Localization;
using MyApp.LocalizationResources;
using MyApp.Models;
namespace MyApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>( options => { options.SignIn.RequireConfirmedAccount = true; }).AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
var cultures = new []
{
new CultureInfo("de"),
new CultureInfo("en"),
};
services.AddControllersWithViews()
.AddExpressLocalization<LocSource>(ops =>
{
ops.UseAllCultureProviders = false;
ops.ResourcesPath = "LocalizationResources";
ops.RequestLocalizationOptions = o =>
{
o.SupportedCultures = cultures;
o.SupportedUICultures = cultures;
o.DefaultRequestCulture = new RequestCulture("en");
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/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();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseRequestLocalization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{culture=en}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "Features",
pattern: "{culture=en}/{controller=Features}/{action=Features}/{id?}");
endpoints.MapControllerRoute(
name: "About",
pattern: "{culture=en}/{controller=About}/{action=About}/{id?}");
endpoints.MapControllerRoute(
name: "Help",
pattern: "{culture=en}/{controller=Help}/{action=Help}/{id?}");
endpoints.MapRazorPages();
});
}
}
}
Register.cshtml.cs (我认为这里有问题,因为我的 MVC 页面没有空值 returnUrl
)
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using MyApp.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using LocSourceNameReferenceLibrary;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Http;
namespace MyApp.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
RegisterPageLocSourceNames _locSourceRegisterPageNameReferenceLibrary = new RegisterPageLocSourceNames();
SharedCrossPageLocSourceNames _locSourceSharedCrossPageNameReferenceLibrary = new SharedCrossPageLocSourceNames();
public RegisterModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public string PageTabTitle { get; set; }
public string Title { get; set; }
public string SubTitle { get; set; }
public string Heading { get; set; }
public string ServiceHeading { get; set; }
public string EmailHeading { get; set; }
public string PasswordHeading { get; set; }
public string ConfirmPassword { get; set; }
public string RegisterButtonName { get; set; }
public string NavBarHome { get; set; }
public string NavBarFeatures { get; set; }
public string NavBarAbout { get; set; }
public string NavBarHelpCenter { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm Password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
PageTabTitle = _locSourceRegisterPageNameReferenceLibrary.GetLocSourcePageTabTitleNameReferenceForRegisterPage();
Title = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceTitleNameReferenceForRegisterPage();
SubTitle = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceSubtitleNameReferenceForRegisterPage();
Heading = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceHeadingNameReferenceForRegisterPage();
ServiceHeading = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceServiceHeadingNameReferenceForRegisterPage();
EmailHeading = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceEmailHeadingNameReferenceForRegisterPage();
PasswordHeading = _locSourceRegisterPageNameReferenceLibrary.GetLocSourcePasswordHeadingNameReferenceForRegisterPage();
ConfirmPassword = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceConfirmPasswordHeadingNameReferenceForRegisterPage();
RegisterButtonName = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceRegisterButtonNameReferenceForRegisterPage();
NavBarHome = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceNavBarHomeReferenceForRegisterPage();
NavBarFeatures = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceNavBarFeaturesReferenceForRegisterPage();
NavBarAbout = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceNavBarAboutReferenceForRegisterPage();
NavBarHelpCenter = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceNavBarHelpCenterReferenceForRegisterPage();
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
}
区域 > 身份 > 页面 > 帐户 > _ViewImports.cshtml
@using MyApp
@using MyApp.Areas.Identity.Pages.Account
@using LazZiya.ExpressLocalization
@inject ISharedCultureLocalizer _loc
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, LazZiya.TagHelpers
@addTagHelper *, LazZiya.ExpressLocalization
我还根据 these instructions 添加了 RouteTemplateModelConvention.cs class,但我认为它没有任何作用。它在评论中说它应该被自动调用。我已经放在根目录了 ~/RouteTemplateModelConvention.cs
这是我的文件夹结构:
我猜是因为身份页面的脚手架自动使用 Razor 页面进行注册和登录,所以我的 MVC 本地化实现不适用于这些。
但是...如果我手动将区域性("en" 或 "de")添加到浏览器 URL,它 是 工作,它只是没有自动拾取它。
我已经尝试了多种变体来使它正常工作,但我确信它很简单,我在 cookie 的路由中丢失了...有什么想法吗?
更新:
我尝试在我的共享 _Layout.cshtml
文件中使用 asp-route-culture=“@CultureInfo.CurrentCulture.Name”
将文化参数添加到 <a></a>
link,但是我收到一条错误消息说 @CultureInfo
在当前上下文中不存在。
更新:
好的进展...
我将 @using System.Globalization
命名空间添加到我的布局文件中。
现在 URL 是 https://localhost:44305/"de"/Identity/Account/Register
我想弄清楚为什么引号在 URL 中。有什么想法吗?
使用 asp-route-culture="@CultureInfo.CurrentCulture.Name"
应该可以解决问题。
在您的代码中,您似乎使用了引号“...”
它们应该是引号 "..."
:)
asp.net-mvc-routing
asp.net-mvc-scaffolding
asp.net-identity
asp.net-core-localization
lazziya.expresslocalization
我已经在我的 Asp.Net Core MVC 3.0 应用程序中实现了这个用于本地化的 nuget 包,我发现它很棒。
https://github.com/LazZiya/ExpressLocalization
除了我的脚手架身份页面之外的所有页面都工作正常。
当我导航到我的脚手架 Register.cshtml 页面时,我遇到了一个问题,我的本地化 cookie 没有被放入 URL。但是,如果我手动将文化(例如 de
或 en
等)键入 URL,则本地化工作正常。
例如,当我点击注册 link时,URL应该是
但它总是
Startup.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.EntityFrameworkCore;
using MyApp.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using LazZiya.ExpressLocalization;
using Microsoft.AspNetCore.Localization;
using MyApp.LocalizationResources;
using MyApp.Models;
namespace MyApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>( options => { options.SignIn.RequireConfirmedAccount = true; }).AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
var cultures = new []
{
new CultureInfo("de"),
new CultureInfo("en"),
};
services.AddControllersWithViews()
.AddExpressLocalization<LocSource>(ops =>
{
ops.UseAllCultureProviders = false;
ops.ResourcesPath = "LocalizationResources";
ops.RequestLocalizationOptions = o =>
{
o.SupportedCultures = cultures;
o.SupportedUICultures = cultures;
o.DefaultRequestCulture = new RequestCulture("en");
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/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();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseRequestLocalization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{culture=en}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "Features",
pattern: "{culture=en}/{controller=Features}/{action=Features}/{id?}");
endpoints.MapControllerRoute(
name: "About",
pattern: "{culture=en}/{controller=About}/{action=About}/{id?}");
endpoints.MapControllerRoute(
name: "Help",
pattern: "{culture=en}/{controller=Help}/{action=Help}/{id?}");
endpoints.MapRazorPages();
});
}
}
}
Register.cshtml.cs (我认为这里有问题,因为我的 MVC 页面没有空值 returnUrl
)
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using MyApp.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using LocSourceNameReferenceLibrary;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Http;
namespace MyApp.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
RegisterPageLocSourceNames _locSourceRegisterPageNameReferenceLibrary = new RegisterPageLocSourceNames();
SharedCrossPageLocSourceNames _locSourceSharedCrossPageNameReferenceLibrary = new SharedCrossPageLocSourceNames();
public RegisterModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public string PageTabTitle { get; set; }
public string Title { get; set; }
public string SubTitle { get; set; }
public string Heading { get; set; }
public string ServiceHeading { get; set; }
public string EmailHeading { get; set; }
public string PasswordHeading { get; set; }
public string ConfirmPassword { get; set; }
public string RegisterButtonName { get; set; }
public string NavBarHome { get; set; }
public string NavBarFeatures { get; set; }
public string NavBarAbout { get; set; }
public string NavBarHelpCenter { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm Password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
PageTabTitle = _locSourceRegisterPageNameReferenceLibrary.GetLocSourcePageTabTitleNameReferenceForRegisterPage();
Title = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceTitleNameReferenceForRegisterPage();
SubTitle = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceSubtitleNameReferenceForRegisterPage();
Heading = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceHeadingNameReferenceForRegisterPage();
ServiceHeading = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceServiceHeadingNameReferenceForRegisterPage();
EmailHeading = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceEmailHeadingNameReferenceForRegisterPage();
PasswordHeading = _locSourceRegisterPageNameReferenceLibrary.GetLocSourcePasswordHeadingNameReferenceForRegisterPage();
ConfirmPassword = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceConfirmPasswordHeadingNameReferenceForRegisterPage();
RegisterButtonName = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceRegisterButtonNameReferenceForRegisterPage();
NavBarHome = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceNavBarHomeReferenceForRegisterPage();
NavBarFeatures = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceNavBarFeaturesReferenceForRegisterPage();
NavBarAbout = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceNavBarAboutReferenceForRegisterPage();
NavBarHelpCenter = _locSourceRegisterPageNameReferenceLibrary.GetLocSourceNavBarHelpCenterReferenceForRegisterPage();
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
}
区域 > 身份 > 页面 > 帐户 > _ViewImports.cshtml
@using MyApp
@using MyApp.Areas.Identity.Pages.Account
@using LazZiya.ExpressLocalization
@inject ISharedCultureLocalizer _loc
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, LazZiya.TagHelpers
@addTagHelper *, LazZiya.ExpressLocalization
我还根据 these instructions 添加了 RouteTemplateModelConvention.cs class,但我认为它没有任何作用。它在评论中说它应该被自动调用。我已经放在根目录了 ~/RouteTemplateModelConvention.cs
这是我的文件夹结构:
我猜是因为身份页面的脚手架自动使用 Razor 页面进行注册和登录,所以我的 MVC 本地化实现不适用于这些。
但是...如果我手动将区域性("en" 或 "de")添加到浏览器 URL,它 是 工作,它只是没有自动拾取它。
我已经尝试了多种变体来使它正常工作,但我确信它很简单,我在 cookie 的路由中丢失了...有什么想法吗?
更新:
我尝试在我的共享 _Layout.cshtml
文件中使用 asp-route-culture=“@CultureInfo.CurrentCulture.Name”
将文化参数添加到 <a></a>
link,但是我收到一条错误消息说 @CultureInfo
在当前上下文中不存在。
更新:
好的进展...
我将 @using System.Globalization
命名空间添加到我的布局文件中。
现在 URL 是 https://localhost:44305/"de"/Identity/Account/Register
我想弄清楚为什么引号在 URL 中。有什么想法吗?
使用 asp-route-culture="@CultureInfo.CurrentCulture.Name"
应该可以解决问题。
在您的代码中,您似乎使用了引号“...”
它们应该是引号 "..."
:)