I have an ASP.Net Core 3.0 MVC project in which I implemented Express Localization for MVC as per the github ExpressLocalizationSampleCore3MVC project . This works ok.
I then scaffolded identity into the project. This works ok.
At this point when I clicked my 'Register' link I was losing the culture cookie i.e the app was navigating to page https://localhost:44305/Identity/Account/Register. If I typed in the localisation url manually, for example https://localhost:44305/de/Identity/Account/Register it worked.
So I decided to try and implement the identity pages as per the ExpressLocalizationSampleCore3 sample project. Unfortunately I am now getting an error:
An unhandled exception occurred while processing the request.
RoutePatternException: The route parameter name 'culture' appears more than one time in the route template.
Microsoft.AspNetCore.Routing.Patterns.RoutePatternParser.Parse(string pattern)
So I have 2 questions please:
1) Do you have a localization example for an MVC project with scaffolded entity framework identity pages?
2) If not, what am I doing wrong below?
Thank you.
Project File Structure:
Areas
Identity
Pages
_ViewImports.cshtml
_ViewStart.cshtml
_ValidationScriptsPartial.cshtml
Account
_ViewImports.cshtml
Login.cshtml
Register.cshtml
Controllers
LocalizationResources
LocSource.cs
LocSource.de.resx
LocSource.en.resx
LocSourceData
LocSourceViews
Models
Views
_ViewImports.cshtml
_ViewStart.cshtml
SetCultureCookie.cshtml
Shared
_CookieConsentPartial.cshtml
_Layout.cshtml
_LoginPartial.cshtml
_ValidationScriptsPartial.cshtml
Error.cshtml
Program.cs
Startup.cs
Identity > Pages > Account > _ViewImports.cshtml
@using MyApp
@using MyApp.Areas.Identity.Pages.Account
Identity > Pages > Account > Register.cshtml
@page
@model RegisterModel
@using LazZiya.ExpressLocalization
@inject ISharedCultureLocalizer _loc
@{
ViewData["Title"] = _loc[Model.PageTabTitle];
var culture = System.Globalization.CultureInfo.CurrentCulture.Name;
}
@section header_image{
<div class="bg-img-non-home" id="FeaturesPageBanner">
}
@section header_content{
<div class="container-title">
<div class="block-title">
<br>
<h1 class="block-subtitle-text" id="FeaturesPageSubtitle">@_loc[Model.SubTitle]</h1>
<h2 class="block-title-text-non-home" id="FeaturesPageTitle">@_loc[Model.Title]</h2>
</div>
</div>
}
Identity > Pages > Account > Register.cshtml.cs
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 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();
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();
}
public IActionResult OnGetSetCultureCookie(string cltr, string returnUrl)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(cltr)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
return LocalRedirect(returnUrl);
}
}
}
IdentityHostingStartup.cs
[assembly: HostingStartup(typeof(MyApp.Areas.Identity.IdentityHostingStartup))]
namespace MyApp.Areas.Identity
{
public class IdentityHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => {
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(context.Configuration.GetConnectionString("DefaultConnection")));
});
}
}
}
Views > _ViewImports.cshtml
@using MyApp
@using MyApp.Models
@using MyApp.Data
@using LazZiya.ExpressLocalization
@namespace MyApp.Views
@inject ISharedCultureLocalizer _loc
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, LazZiya.TagHelpers
@addTagHelper *, LazZiya.ExpressLocalization
Views > SetCultureCookie.cshtml
@page
@model MyApp.Views.SetCultureCookieModel
@{
ViewData["Title"] = "Redirect to";
}
<h1>Redirect to</h1>
~> Startup.cs
using LazZiya.ExpressLocalization;
using Microsoft.AspNetCore.Localization;
using MyApp.LocalizationResources;
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.AddRazorPages()
.AddExpressLocalization<LocSource>(ops =>
{
// When using all the culture providers, the localization process will
// check all available culture providers in order to detect the request culture.
// If the request culture is found it will stop checking and do localization accordingly.
// If the request culture is not found it will check the next provider by order.
// If no culture is detected the default culture will be used.
// Checking order for request culture:
// 1) RouteSegmentCultureProvider
// e.g. http://localhost:1234/tr
// 2) QueryStringCultureProvider
// e.g. http://localhost:1234/?culture=tr
// 3) CookieCultureProvider
// Determines the culture information for a request via the value of a cookie.
// 4) AcceptedLanguageHeaderRequestCultureProvider
// Determines the culture information for a request via the value of the Accept-Language header.
// See the browsers language settings
// Uncomment and set to true to use only route culture provider
//ops.UseAllCultureProviders = false;
ops.ResourcesPath = "LocalizationResources";
ops.RequestLocalizationOptions = o =>
{
o.SupportedCultures = cultures;
o.SupportedUICultures = cultures;
o.DefaultRequestCulture = new RequestCulture("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();
});
}
}
}
~> Program.cs
namespace MyApp
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}