I have implemented this nuget package for localization in my Asp.Net Core MVC 3.0 app and have found it great.
https://github.com/LazZiya/ExpressLocalization
For all pages except my scaffolded identity pages it is working fine.
When I navigate to my scaffolded Register.cshtml page I am having a problem where my localization cookie is not being placed into the URL. But if I type the culture (Such as de
or en
etc.) into the URL manually the localization works fine.
For example, when I click the Register link the URL should be
but instead it is always
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 (I think something is wrong here because my MVC Pages do not have a null 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();
}
}
}
Areas > Identity > Pages > Account > _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
I have also added a RouteTemplateModelConvention.cs class as per these instructions, but I don't think it is doing anything. It says in the comments it should be automatically invoked. I have placed it in the root ~/RouteTemplateModelConvention.cs
This is my folder structure:
I'm guessing that because the scaffolding of the identity pages automatically used Razor pages for Register and Login, my MVC Localization implementation is not working for these.
But...it is working if I manually add the culture ("en" or "de" ) into the browser URL, its just not picking it up automatically.
I have tried any number of variations to get this working, but am sure it is something simple I'm missing in the routing for the cookie...Any ideas please?
Update:
I have tried adding the culture parameter to the <a></a>
link using asp-route-culture=“@CultureInfo.CurrentCulture.Name”
in my shared _Layout.cshtml
file, but am getting an error saying that @CultureInfo
does not exist in the current context.
Update:
Ok progress...
I added the @using System.Globalization
namespace to my layout file.
Now the URL is https://localhost:44305/"de"/Identity/Account/Register
I'm trying to figure out why the inverted commas are in the URL. Any ideas?
Using asp-route-culture="@CultureInfo.CurrentCulture.Name"
should fix the issue.
In your code it seems that you are using inverted commas“...”
they should be quotation marks "..."
:)