Невозможно сгенерировать ответный запрос ie для локализации с использованием ASP. NET CORE 2.2. - PullRequest
2 голосов
/ 15 января 2020

Я не буду вам 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 . У кого-нибудь есть идеи относительно того, что может быть причиной этого странного явления?

1 Ответ

0 голосов
/ 15 января 2020

Порядок services.AddMvc() и services.Configure<RequestLocalizationOptions> важен. Убедитесь, что все, включая локализацию, выполняется до AddMvC.

См. https://docs.microsoft.com/en-us/aspnet/core/fundamentals/troubleshoot-aspnet-core-localization?view=aspnetcore-3.1:

Порядок промежуточного программного обеспечения для локализации Приложение может не локализоваться из-за локализации промежуточное программное обеспечение не заказано, как ожидалось. Чтобы решить эту проблему, убедитесь, что промежуточное ПО локализации зарегистрировано до промежуточного программного обеспечения MVC. В противном случае промежуточное программное обеспечение для локализации не применяется

Вчера я создал следующую настройку в ASP. NET Core 3.1:

Startup.cs в Configure До .AddMvc:

app.UseRequestLocalization();

Я также думаю, что вам не хватает пары строк в RequestLocalizationOptions:

CultureInfo[] supportedCultures = new[]
{
    new CultureInfo("en-US"),
    new CultureInfo("ko-KR")
};
services.Configure<RequestLocalizationOptions>(options =>
{
    options.DefaultRequestCulture = new RequestCulture("en-US");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;
    options.RequestCultureProviders = new List<IRequestCultureProvider>
    {
        new QueryStringRequestCultureProvider(),
        new CookieRequestCultureProvider()
    };
});

Вы не указали CookieRequestCultureProvider который слушает повара ie. Это должно быть второй причиной, по которой ваш пример не работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...