Я не буду вам 1053 *. Я почти сошел с ума от этой проблемы. Я потратил около 4 часов своего времени, пытаясь каждое решение в книге исправить проблему, которая, как я знаю, была очень распространена для программистов, пытающихся внедрить локализацию в свои веб-приложения. Всякий раз, когда я пытаюсь изменить язык моей веб-страницы с Engli sh (en-US) на корейский (ko-KR), он по умолчанию возвращается к Engli sh, который установлен по умолчанию. Я сузил проблему и знаю, что я не генерирую правильный ответ cook ie, но ни одно из решений, которые я нашел в Интернете для этой, по-видимому, VERY общей проблемы, не помогло мне.
Я попытался обновить куки и кеш, я добавил расширения Microsoft.AspNetCore.Localization;
и Microsoft.Extensions.Localization;
, я попытался использовать параметр isEssential
для объекта CookieOptions
, я знаю свой файл структура правильная, и все мои файлы .resx находятся там, где они должны быть, потому что я могу видеть все переводы, которые мне нужны, когда я вручную переключаю сайт на корейский, используя ? culture = ko-KR Я считаю, что я правильно настроил файл startup.cs, файл контроллера и частичное представление, и мне нужен спасательный круг.
Я следовал интерактивному учебнику, чтобы за несколько дней до этого настроить фиктивное веб-приложение учебника, и могу успешно изменить культуру этого веб-приложения. Из-за того, как работают куки, я могу изменить язык только в своем основном приложении, изменив язык в фиктивном веб-приложении, и это вообще невозможно.
Вот важные части моего кода.
startup.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Serialization;
using Serilog;
using Snape.DataLayer.Entities;
using Snape.Web.ScheduledProcessor;
using Snape.Web.Services;
using Snape.WebSecurity.Hashing;
using Snape.WebSecurity.Helpers;
using Snape.WebSecurity.Tokens;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Threading.Tasks;
using System.Linq;
namespace Snape.Web
{
public class Startup
{
private readonly IConfiguration _configProvider;
private readonly SigningConfiguration _signConfig;
private readonly IConfigurationRoot _constantsConfigProvider;
public Startup(IConfiguration configuration)
{
_configProvider = configuration;
_signConfig = new SigningConfiguration();
// Loading Constants.json && Configuration.json
var configurationBuilder = new ConfigurationBuilder()
.AddJsonFile($"{_configProvider.GetSection("Constants").Value}", optional: false, reloadOnChange: true)
.AddJsonFile($"{_configProvider.GetSection("Version").Value}", optional: true, reloadOnChange: true);
_constantsConfigProvider = configurationBuilder.Build();
// Initializing Serilog
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddDistributedMemoryCache(); // Adds a default in-memory implementation of IDistributedCache
services.AddSession(options => options.IdleTimeout = TimeSpan.FromHours(1));
/* Note this is commented out.
var cookieOptions = new Microsoft.AspNetCore.Http.CookieOptions()
{
Path = "/",
HttpOnly = false,
IsEssential = true, //<- there
Expires = DateTime.Now.AddMonths(1),
}; */
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// we need to add localization to the project for views, controllers, and data annotations.
services.AddMvc()
// localization options are going to have their resources (language dictionary) stored in Resources folder.
.AddViewLocalization(opts => { opts.ResourcesPath = "Resources"; })
.AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// we are configuring the localization service to support a list of provided cultures.
services.Configure<RequestLocalizationOptions>(opts =>
{
// the list of supported cultures.
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en"),
new CultureInfo("en-US"),
new CultureInfo("ko"),
new CultureInfo("ko-KR"),
};
// set the localization default culture as english
opts.DefaultRequestCulture = new RequestCulture("en-US");
// supported cultures are the supportedCultures variable we defined above.
// formatiting dates, numbers, etc.
opts.SupportedCultures = supportedCultures;
// UI strings that we have localized
opts.SupportedUICultures = supportedCultures;
});
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
services.AddDbContext<SnapeDbContext>(options => options.UseLazyLoadingProxies().UseSqlite(_configProvider.GetConnectionString("SnapeDbConnection")));
services.AddSingleton(_constantsConfigProvider); // IConfigurationRoot
// *If* you need access to generic IConfiguration this is **required**
services.AddSingleton(_configProvider);
// Background task for data push
services.AddSingleton<IHostedService, DataPushingTask>();
// Background task for device's state check
services.AddSingleton<IHostedService, HeartbeatTask>();
// Background task for project's sync with cloud
services.AddSingleton<IHostedService, SyncingTask>();
// Background task for Purging
services.AddSingleton<IHostedService, PurgingTask>();
// Service for Internet Management
services.AddTransient<InternetService>();
services.Configure<TokenOptions>(_configProvider.GetSection("TokenOptions"));
var tokenOptions = _configProvider.GetSection("TokenOptions").Get<TokenOptions>();
services.AddSingleton<IPassportHasher, PasswordHasher>();
services.AddSingleton<ITokenHelper, TokenHelper>();
services.AddSingleton(_signConfig);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(jwtBearerOptions =>
{
jwtBearerOptions.RequireHttpsMetadata = false;
jwtBearerOptions.SaveToken = true;
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = tokenOptions.Issuer,
ValidAudience = tokenOptions.Audience,
IssuerSigningKey = _signConfig.Key,
ClockSkew = TimeSpan.Zero
};
});
services.Configure<FormOptions>(options =>
{
options.ValueCountLimit = int.MaxValue;
options.ValueLengthLimit = 1024 * 1024 * 100; // 100MB max len form data
});
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-AU");
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-AU");
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
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();
}
// Enabling Logger
loggerFactory.AddSerilog();
app.UseHttpsRedirection();
app.UseStaticFiles();
// specify that globalization is being used in the pipeline.
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
app.UseCookiePolicy();
app.UseSession();
//Add JWToken to all incoming HTTP Request Header
app.Use(async (context, next) => {
var jwToken = context.Session.GetString("JWToken");
if (!string.IsNullOrEmpty(jwToken))
{
context.Request.Headers.Add("Authorization", "Bearer " + jwToken);
}
await next();
});
app.UseAuthentication();
app.UseStatusCodePages(context => {
var response = context.HttpContext.Response;
if (response.StatusCode == (int)HttpStatusCode.Unauthorized || response.StatusCode == (int)HttpStatusCode.Forbidden)
{
response.Redirect("/Account/Login");
if (Utilities.WebUtility.IsAjaxRequest(context.HttpContext.Request))
response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
return Task.CompletedTask;
});
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Account}/{action=Login}/{id?}");
routes.MapRoute(
"invalid_route",
"{*url}",
new { controller = "NotFound", action = "Index" });
});
#if RELEASE
if (_constantsConfigProvider.GetValue<bool>("CELLULAR_ON"))
{
Task.Run(async () => { await app.ApplicationServices.GetRequiredService<InternetService>().Enable(); });
}
#endif
}
}
}
AccountController.cs
public class AccountController : BaseController
{
// the localizer dictionary to translate languages for this controller.
readonly IStringLocalizer<AccountController> _localizer;
readonly IConfiguration _configProvider;
readonly IPersonFacade _personFacade;
readonly SnapeDbContext _dbContext;
readonly ITokenHelper _tokenHelper;
// AccountController constructor
public AccountController(IStringLocalizer<AccountController> localizer, SnapeDbContext dbContext, IConfiguration configuration, IPassportHasher passwordHasher, ITokenHelper tokenHandler,
IConfigurationRoot constantsConfig) : base(constantsConfig)
{
// initialize the localizer.
_localizer = localizer;
_dbContext = dbContext;
_tokenHelper = tokenHandler;
_configProvider = configuration;
_personFacade = new PersonFacade(dbContext);
}
[HttpPost] // annotation that specifies that this action is called on an HTTPPost
// this method needs to persist on both this page and any subsequent ones. Sets cookie for changed culture.
public IActionResult SetLanguage(string culture, string returnURL)
{
// set the cookie on the local machine of the Http Response to keep track of the language in question.
// append the cookie and its language options.
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName, // name of the cookie
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)), // create a string representation of the culture for storage
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddDays(1),
IsEssential = true, //<- there
} // expiration after one day.
);
return LocalRedirect(returnURL); // redirect to the original URL, the account page.
}
_SelectLanguagePartial.cs html
@using Microsoft.AspNetCore.Builder
@using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization
@using Microsoft.Extensions.Options
@inject IViewLocalizer Localizer
@inject IOptions<RequestLocalizationOptions> LocOptions
@{
// this code finds out what cultures I am supporting.
// it is all defined in startup.cs
var requestCulture = Context.Features.Get<IRequestCultureFeature>();
var cultureItems = LocOptions.Value.SupportedUICultures // all the supported cultures.
.Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
.ToList();
}
<!-- Partial view in ASP.NET MVC is special view which renders a portion of view content. It is just like a user control of a web form application.
Partial views can be reusable in multiple views. It helps us to reduce code duplication. In other words a partial view enables us to render a view within the parent view.
This partial view will be placed inside the layout.cshtml file, which is a shared (this is key) view that is under the wing of the home controller, just like the Home Views are -->
<!-- This code displays the culture/language dropdown.-->
<!-- Title of the dropdown-->
<div title="@Localizer["Request culture provider:"] @requestCulture?.Provider?.GetType().Name">
<!-- another post method-->
<!-- this form will call the setLanguage method under the AccountController.cs file. Even though this is a shared view, it's shared nature means the AccountController can still see it and act off of it.-->
<form id="selectLanguage" asp-controller="Account" asp-action="SetLanguage" asp-route-returnUrl="@Context.Request.Path"
method="post" class="form-horizontal" role="form">
<!-- Select dropdown for the language selection -->
<!-- asp-for indicates -->
<a style="color:white"> @Localizer["Language"]</a>
<select name="culture" asp-for="@requestCulture.RequestCulture.UICulture.Name" asp-items="cultureItems"></select>
<button type="submit" class="btn btn-default btn-xs">Save</button>
<!-- clicking on the save button will call the action setLanguage in the AccountController.-->
</form>
</div>
частичный Asyn c вызов
<!-- import the partial view for selecting languages _SelectLanguagePartial.cshtml -->
@await Html.PartialAsync("_SelectLanguagePartial");
Если кто-нибудь может пролить свет на то, куда go отсюда, я бы действительно ценю это. Я не хочу больше вырывать волосы из-за этого. Последнее, что я пытался установить isEssential = true
в CookieOptions
, чтобы переопределить следующую конфигурацию запроса, определенную в startup.cs
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
ВАЖНОЕ РЕДАКТИРОВАНИЕ: Я не знаю Не знаю, как и почему, но мне удалось заставить локализацию работать на отдельной странице в моем веб-приложении, доступ к которой после . Пользователь успешно входит в систему с именем пользователя и паролем. , Выбранный язык сохраняется, если страница меняется, и это то, что я ищу, даже если я вернусь на страницу входа. Это хорошо, но я все еще не могу изменить язык или культуру со страницы входа в систему моего веб-приложения, внутренняя функциональность которого обрабатывается AccountController.cs . У кого-нибудь есть идеи относительно того, что может быть причиной этого странного явления?