Can I use IStringLocalizer in a class which is not a Controller
Asked Answered
A

2

12

I understand how to use the IStringLocalizer interface in a class which derives from Controller, for example as described here or here.

What would be the correct way to use IStringLocalizer in a class which does not derive from Controller?

I cannot find any examples addressing this question.

Is it always necessary to pass in an IStringLocalizer or IStringLocalizerFactory to the constructor?

Note.
I know that this is a fairly generic question (and Stack Overflow is for concrete programming questions). The background is that I make a localization tool for .NET projects. I am trying to figure out what changes my tool has to make to the source code to support localization in ASP.NET Core projects.

Ainslee answered 27/5, 2017 at 7:51 Comment(5)
IStringLocalizer is resolved through ASP.NET Core built-in DI container. You could inject IStringLocalizer in constuctor in any class which is resolved through DI too. Could you try it?Plasty
Thanks @IlyaChumakov, I will try it. I have found another question on SO with a good explanation here. In fact my question is close to being a duplicate.Ainslee
Yes, it looks like this.Plasty
github.com/aspnet/Localization/blob/…Italia
github.com/aspnet/Localization/blob/…Italia
A
17

It doesn't matter if a class derives from Controller.

It is important that your objects are created by the Dependency Injection Container and not by using the new operator, as described here. If you want to use the ASP.NET Core localization mechanism in all your classes, you must adopt this pattern for object creation.

This page gives a good description of how to create objects, but I will try to give an example of how it works with localization.

I have created a class MyHelper. The class expects the localizer object to be passed in to the constructor. It contains a single property Hello, which returns a localized string.

namespace AddingLocalization.Classes
{
  public class MyHelper
  {
    private readonly IStringLocalizer<MyHelper> _localizer;

    public MyHelper(IStringLocalizer<MyHelper> localizer)
    {
      _localizer = localizer;
    }

    public string Hello
    {
      get
      {
        return _localizer["Hello World."];
      }
    }

  }
}

In the ConfigureServices method in the Startup class, I have added the boilerplate code described here and added a line to register the class MyHelper with the Dependency Injection container.

public void ConfigureServices(IServiceCollection services)
{
  services.AddLocalization(opts => opts.ResourcesPath = "Resources");

  services.AddMvc()
          .AddViewLocalization (
               LanguageViewLocationExpanderFormat.Suffix,
               opts => opts.ResourcesPath = "Resources" )
          .AddDataAnnotationsLocalization();

  // This line registers the class MyHelper with the 
  // Dependency Injection Container.      
  services.AddTransient<MyHelper>();
}

In my controller class, I have added a parameter of type MyHelper to the constructor, which is stored in a member variable.

public class HomeController : Controller
{
  private readonly IStringLocalizer<HomeController> _localizer ;
  private readonly MyHelper                         _h ;

  public HomeController ( IStringLocalizer<HomeController> localizer,
                          MyHelper                         h )
  {
    _localizer = localizer;
    _h         = h ;
  }

  ...

  public IActionResult About()
  {
    ViewData["Message"] = _h.Hello ;
    return View();
  }

  ...
}  

Because the class MyHelper has been registered with the dependency injection container, it creates this object and passes it in to the constructor automatically. This is the magic performed by the dependency injection container.

In the About() method, I fetch the property from the MyHelper object.

That's about it for the code, but I wanted to be certain that it will actually read the string from a resource file.

The naming convention for resource files is described here:

It is based on the full name of the class, without the name of the assembly. In my case class is called AddingLocalization.Classes.MyHelper and the assembly is called AddingLocalization, so the relevant name is Classes.MyHelper.

There are actually two naming conventions, using dots or subdirectories, so we can call the resource file one of

  • Classes.MyHelper.resx
  • Classes\MyHelper.resx

The boilerplate code in ConfigureServices specified the ResourcesPath "Resources", so this is where we must place the resource file. I chose the second naming option, so my resource file is

  • Resources\Classes\MyHelper.resx

as you can see in the solution explorer

2017-05-28_09-30-59.png

We do not need Visual Studio to create a file MyHelper.Designer.cs to access the resources, so we should clear out the CustomTool property for the resource file.

enter image description here

This is important, because the names will not be valid resource names and will probably generate error messages, if we do not disable the custom tool.

(Previous localization methods (from Microsoft) have always used fabricated resource names (e.g. with underscrore instead of space) to access resources. The new ASP.NET core localization uses the original string as the resource name.)

Finally I have defined a resource string as shown below:

enter image description here

I haven't actually tried accessing resources in a different language (yet), but the localizer object did read the resource correctly.

Ainslee answered 28/5, 2017 at 7:59 Comment(1)
cool helper, much better to write just Error instead of _localizer["hello world"], but you lose ability to format it like _localizer["hello world", "arg"]Italia
E
0

You can move the Shared resource Files (sharedResource.en-US.resx, sharedResource.ko-kr and etc ) to the Business layer library by creating a folder called "Resources" instead of on the Web/API project. But make sure that resource location relative path's value on the Startup should be empty.

services.AddLocalization(options => options.ResourcesPath = "");

Your SharedResouce.cs file looks like below.

namespace your.business.library.namespace.Resources
{
    public interface ISharedResource
    {
    }
    public class SharedResource : ISharedResource
    {
        private readonly IStringLocalizer _localizer;

        public SharedResource(IStringLocalizer<SharedResource> localizer)
        {
            _localizer = localizer;
        }

        public string this[string index]
        {
            get
            {
                return _localizer[index];
            }
        }
    }
}

define the filed to access the business class required.

private readonly IStringLocalizer<SharedResource> _localizer;

    public AppointmentBookingBL(IStringLocalizer<SharedResource> localizer)
    {
        _localizer = localizer;
    }

fetch the message from a resource like below

      public void ModifyBooking(BookingModel model)
        {
            ......... code here
            
                var message = _localizer["BOOKINGNOTAVAILABLE"];
               
            ..... code here
        }
Ertha answered 18/3, 2021 at 9:5 Comment(1)
I tried but in message I get "BOOKINGNOTAVAILABLE" and not the value associated with. I added Microsoft.Extensions.Localization.Abstractions too. It's strange I never go to SharedResource class.Squall

© 2022 - 2024 — McMap. All rights reserved.