Skip to content

Relating to issue # https://github.com/dotnet/corefx/issues/16985#issuecomment-286142692 #1965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
deepak-khopade-isdr opened this issue Mar 13, 2017 · 23 comments
Assignees

Comments

@deepak-khopade-isdr
Copy link

Please refer: https://github.com/dotnet/corefx/issues/16985#issuecomment-286142692

@Tratcher
Copy link
Member

@ryanbrandenburg Loc issue

@ryanbrandenburg
Copy link
Contributor

It sounds like you're having trouble with resource naming. You seem to have already read https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization but please pay extra attention to the info under "Resource file naming".

This kind of problem is usually caused by confusion about namespaces, please double check that the namespace of the class you're creating a resource for is the same as the name of the assembly for your project in the bin folder. For example if your assembly is "Localization.Project.dll" then to get the class Startup with just Startup.resx Startup must be in the namespace Localization.Project.

If you're still having trouble after double checking your namespace please upload a sample which reproduces your problem to GitHub for us to review and debug against.

@deepak-khopade-isdr
Copy link
Author

@ryanbrandenburg : yes I read that and named my resource file accordingly. Since I just want to use single .resx common for entire project, I have named it as Startup.resx and my project's name is "app".
so in "/app/bin/Docker/netcoreapp1.1/en", I have "app.resources.dll" which i suppose should have a namespace as "app.resources.Startup"... is this correct? if so then it should work... right?

Can you please give a realistic example of how the naming should be right from the project till .resx file?

@deepak-khopade-isdr
Copy link
Author

I just dont want to use Controller specific or feature resx... want to have 1 general for now which is for whole project and thats why created it with name after "Startup.cs" which is at root. and the resx file is under /resources folder and /resources folder is also at root of the project.

@ryanbrandenburg
Copy link
Contributor

Just to clarify, you're saying the namespace of Startup is app.resources? If so that's your problem. You want to set the namespace based off the name of the main assembly (it looks like that would be under /app/bin/Docker/netcoreapp1.1 for you), not the satelite assemblies we use for resources. I'm guessing that your main assembly is app.dll and therefore the correct namespace for Startup would be app.

We're trying to clarify the docs in this area because we get a lot of issues filed about it, was there anything specific that made you think to look at the assembly in /en/, so we can clarify it?

@deepak-khopade-isdr
Copy link
Author

No the namespace of Startup.cs is just an app and that's correct it creats app.dll for my project's build. So not sure where my namespacing strategies are wrong. Let me create a screenshot for my project structure.

@deepak-khopade-isdr
Copy link
Author

startup_cs_

@deepak-khopade-isdr
Copy link
Author

Code in Startup.cs :

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using Microsoft.EntityFrameworkCore;
using app.common.exceptions.filters;
using app.common.db;
using app.common.config;
using app.common.extensions;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Localization;

namespace app
{
    public class Startup
    {
        private const string ExceptionsOnStartup = "Startup";

        private const string ExceptionsOnConfigureServices = "ConfigureServices";

        private readonly Dictionary<string, List<Exception>> _exceptions;

        public IHostingEnvironment HostingEnvironment { get; }

        public ILoggerFactory LoggerFactory { get; }

        public Startup(IHostingEnvironment env, ILoggerFactory logger, string[] args = null)
        {
            _exceptions = new Dictionary<string, List<Exception>>
            {
                { ExceptionsOnStartup, new List<Exception>() },
                { ExceptionsOnConfigureServices, new List<Exception>() },
            };

            try
            {
                HostingEnvironment = env;
                LoggerFactory = logger;

            }
            catch (Exception ex)
            {
                _exceptions[ExceptionsOnStartup].Add(ex);
            }
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            try
            {
                // Add MVC services.
                services
                    .AddLocalization(options => options.ResourcesPath = "resources")
                    .AddMvc()
                    .AddMvcOptions(options =>
                    {
                        options.Filters.Add(new GlobalActionFilter(LoggerFactory));
                        options.Filters.Add(new GlobalExceptionFilter(LoggerFactory));
                    })
                    .AddJsonOptions(options => {
                        options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                        options.SerializerSettings.ContractResolver = new DefaultContractResolver();
                        options.SerializerSettings.Converters.Add(new StringEnumConverter());
                        options.SerializerSettings.Formatting = Formatting.Indented;
                        options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
                        options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
                    });

                // Add other services
                services
                    .AddEntityFrameworkMySql()
                    .AddDbContext<AppDb>(options => options.UseMySql(AppConfig.Config["Data:DefaultConnection:ConnectionString"]))
                    .AddCors(options =>
                    {
                        options.AddPolicy("CorsPolicy",
                            builder => builder.WithOrigins("http://localhost:3000")
                                .AllowCredentials()
                                .AllowAnyHeader()
                                .AllowAnyMethod());
                    })
                    .RegisterServices();
            }
            catch (Exception ex)
            {
                _exceptions[ExceptionsOnConfigureServices].Add(ex);
            }
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IStringLocalizer<Startup> stringLocalizer, IStringLocalizerFactory stringLocalizerFactory)
        {
            loggerFactory.AddConsole(AppConfig.Config.GetSection("Logging"));
            loggerFactory.AddDebug();

            //add localization support
            var supportedCultures = new[]
            {
                new CultureInfo("en-US")
            };
            app.UseRequestLocalization(new RequestLocalizationOptions
            {
                DefaultRequestCulture = new RequestCulture("en-US"),
                SupportedCultures = supportedCultures,
                SupportedUICultures = supportedCultures
            });

            var log = loggerFactory.CreateLogger<Startup>();
            if (_exceptions.Any(p => p.Value.Any()))
            {
                app.Run(async context => {
                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    context.Response.ContentType = "text/html";
                    foreach (var ex in _exceptions)
                    {
                        foreach (var val in ex.Value)
                        {
                            log.LogError($"{ex.Key}:::{val.Message}");
                            await context.Response.WriteAsync($"Error on {ex.Key}: {val.Message}").ConfigureAwait(false);
                        }
                    }
                });
                return;
            }

            try
            {
                app.UseExceptionHandler(builder => {
                    builder.Run(async context => {
                        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                        context.Response.ContentType = "text/html";
                        var error = context.Features.Get<IExceptionHandlerFeature>();
                        if (error != null)
                            await context.Response.WriteAsync($"<h1>Error: {error.Error.Message}</h1>").ConfigureAwait(false);
                    });
                });

                if (Environment.GetEnvironmentVariable("DEVENV") == "dev")
                    app.UseDeveloperExceptionPage();

                app.UseStaticFiles();

                app.UseStatusCodePages();
                app.UseCors("CorsPolicy");
                app.UseMvc();
            }
            catch (Exception ex)
            {
                app.Run(async context => {
                    log.LogError($"{ex.Message}");
                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    context.Response.ContentType = "text/html";
                    await context.Response.WriteAsync(ex.Message).ConfigureAwait(false);
                    await context.Response.WriteAsync(ex.StackTrace).ConfigureAwait(false);
                });
            }
        }

    }
}

@deepak-khopade-isdr
Copy link
Author

Contents of Startup.en-US.resx :

<?xml version="1.0" encoding="utf-8"?>
<root>
    <data name="GET_COLOR_404">
        <value>Color presets not found.</value>
    </data>
    <data name="COLOR_PATCH_400_NULL">
        <value>Color settings cannot be null.</value>
    </data>
    <data name="COLOR_PATCH_400_NOT_UPDATED">
        <value>No data was updated.</value>
    </data>
</root>

@deepak-khopade-isdr
Copy link
Author

deepak-khopade-isdr commented Mar 13, 2017

And the way I am trying to use it in my controller which is under "app.api.controllers".

return NotFound(localizer["GET_COLOR_404"]);

So expected to return the message from .resx file for given key, but it returns not found.

@ryanbrandenburg
Copy link
Contributor

@deepak-khopade-isdr it would be easier to investigate this if you could distill your project into the smallest project that still demonstrates the behavior. That said, I notice that in your Configure method you have parameters for IStringLocalizer<Startup> and IStringLocalizerFactory which are unused. This makes me wonder how the localizer you're using in your controller is generated, could you clarify that?

For reference on how we recommend people use Localization within an MVC project see this projects in Entropy. Thanks for bringing that up, I think I'll go ahead and add that to the Localization readme to make it more discoverable.

@deepak-khopade-isdr
Copy link
Author

deepak-khopade-isdr commented Mar 13, 2017

My controller code below. And I havent implemented it project wide yet and was trying on this first simple controller, however, its not working. I will put everything in order in next comment.

private readonly ILogger<ColorsController> _logger;
        private readonly IStringLocalizer<ColorsController> _localizer;
        private readonly ColorsRepo _colorsRepo;

        public ColorsController(ILogger<ColorsController> logger, IStringLocalizer<ColorsController> localizer, ColorsRepo colorsRepo)
        {
            _logger = logger;
            _colorsRepo = colorsRepo;
            _localizer = localizer;
        }

And then simply using _localizer["KEY_NAME"].Value in my returns whenever I want to return a message and expecting to show the text from .resx file.

@ryanbrandenburg
Copy link
Contributor

You're creating an IStringLocalizer<ColorsController> when you should be creating an IStringLocalizer<Startup>. The type you pass into the localizer should be the type of the class you've created resources for, which for many people would be the current class, but you only have one for Startup. What you've done here is create a localizer which will look at ColorsController.en-US.resx and ColorsController.resx.

@deepak-khopade-isdr
Copy link
Author

Ok, tried the way you explained but no luck :(

@ryanbrandenburg
Copy link
Contributor

Please create a GitHub repo which reproduces your problem and link to it here so that I have something to examine.

@deepak-khopade-isdr
Copy link
Author

deepak-khopade-isdr commented Mar 14, 2017

Alright... here you go: https://github.com/deepak-khopade-isdr/LocalizationWebApi

However, important to mention here. When I ran this on my local (Ubuntu 16.04 and JetBrains Rider IDE) it's working, but when I took the same code on "docker" container, it's not working.

Not working meaning, it's not loading the value from a .RESX file I am expecting. Please run this on docker container to reproduce the error.

Also please let me know the if I am using this Localization feature incorrectly.

@deepak-khopade-isdr
Copy link
Author

Guess what, when I tried my regular app (business app in our private repo) running locally (not in docker), it's working, so looks like something wrong when .NET Core is trying to read the Resource file(s) from mounted systems / drives. Not sure, but my strong guess. Or may be when dealing with resx files on docker containers, I am doing something wrong.

@ryanbrandenburg ryanbrandenburg self-assigned this Mar 14, 2017
@ryanbrandenburg
Copy link
Contributor

What does you DockerFile look like? I ask because if you're doing something like RUN dotnet restore and ENTRYPOINT ["dotnet", "run"] within the DockerFile then it's likely that your project.json is ending up in a differently named folder (and thus have a differently named assembly). If that's the case I suggest adding "name": "LocalizationWebApi", to the top of your project.json to explicitly set your project name and AssemblyName. In fact, that probably good advice for anyone using Localization and I'll add it to the docs.

If you're publishing your project and then doing ENTRYPOINT ["dotnet", "LocalizationWebApi.dll"] that suggestion likely won't solve your problem, but a copy of your DockerFile will help me better understand your scenario.

@deepak-khopade-isdr
Copy link
Author

Sorry I didn't get you completely; what is docker file and where can i find it in my project?

@ryanbrandenburg
Copy link
Contributor

A DockerFile is a file you would create so that other people and systems can reliably create docker images identical to the ones you're producing locally, and thus have the same experience as you (anyone feel free to correct me, I'm new to docker). If you do not have one I think the best course of action would be for you to add "name": "LocalizationWebApi", to your project.json and see if that resolves your problem. If it doesn't fix it we'll have to determine how exactly you're building your docker images so that we can do the same thing and reproduce your issue.

@deepak-khopade-isdr
Copy link
Author

Hi @ryanbrandenburg,

Sorry for late reply on this. We don't have project.json anymore and using .csporj now for all our projects in solution so that we can mix our developer environment with Windows, Ubuntu & MAC. And also to use either VS 2017 CE or Jet Brains Rider.
Anyways, i checked the namespace and all we have is below:-

  <PropertyGroup>
    <TargetFramework>netcoreapp1.1.1</TargetFramework>
    <AssemblyName>app</AssemblyName>
    <OutputType>Exe</OutputType>
  </PropertyGroup>

And for dockerfile I now get what you say but I dont have persmission to share the docker file. I will check with our DevOps engineer if he can give me details about it.

@deepak-khopade-isdr
Copy link
Author

Just FYI: I was reading about Docker Containers and Volumes. And in our project, we are using volume which is obviously a shared file system folder from host OS into Docker container. And when I am running project on Host OS locally (Ubuntu or Windows) using F5 http://localhost:5000/... its working and when running under docker using http://dockerhost:/... its failing;
So do you think that somewhere it's failing to read the .resx file from host os?

@ryanbrandenburg
Copy link
Contributor

This issue was moved to aspnet/Localization#357

ryanbrandenburg pushed a commit that referenced this issue Nov 27, 2018
dougbu pushed a commit to dotnet-maestro-bot/AspNetCore that referenced this issue Sep 11, 2019
Allow PR builds to flow
@ghost ghost locked as resolved and limited conversation to collaborators Dec 4, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants