Recommand · May 26, 2021 0

.Net Core Entity MVC using customized Localization in your Business Logic projects in a multi-layered solution with angular front-end

please edit/improve/correct/adjust

I struggled a lot to piece the pieces together of this (I’m not a very apt coder). This is way of sharing knowledge, as is encouraged by StackOverflow. Please edit/improve/correct.

The issue was:

situation: a multi-layerd MVC-entity code-first solution, with an angular front-end
mission (received): use Localization, in the business logic layer, for translating the headers of a .csv-export, in such a way that we can reuse it later for anything else we want/need
we don’t work with the culture-info-flags, we use-custom-made language flags that are transmitted with every http-request using the Accept-Language field in the header

(both situation and mission were imposed on me)

please edit/improve/correct/adjust

references:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-3.1#implement-a-strategy-to-select-the-languageculture-for-each-request

https://ml-software.ch/posts/writing-a-custom-request-culture-provider-in-asp-net-core-2-1

ASP.NET Core Request Localization Options

https://angular.io/guide/http#intercepting-requests-and-responses

  1. Configure services in the DI container

a. Register the service in
the Startup-method (see created separate classDIConfiguration)

b. Configure and apply options of Localization in the
Configure-method

  1. Define Localizationoptions (CustomRequestCultureProvider Class)

  2. Build up your middleware in the appropriate project (business logic)

  3. Define SharedResource.class and resx-files in the same project

  4. Inject Middleware in business logic-class (handler)

  1. Configure services in the DI container
    a. Register the service in the Startup-method (see created separate classDIConfiguration)
    b. Configure and apply options of Localization in the Configure-method

using …

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();services.AddCors(options =>
            {
                options.AddPolicy(name: MyAllowSpecificOrigins,
                    builder =>
                    {
                        builder.WithOrigins("http://localhost:4200")
                               .AllowAnyHeader()
                               .AllowAnyMethod()
                               .WithExposedHeaders("Content-Disposition");
                    });
            });

            // In production, the Angular files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/dist";
            });
            DiConfiguration.ConfigureServices(Configuration, services);
            AutoMapperConfiguration.ConfigureAutoMapper(services);
            services.AddMediatorAndBehaviour();
        }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            var supportedCultures = new[] { "nl", "en", "fr", "de" };
            var localizationOptions = new RequestLocalizationOptions()
                .SetDefaultCulture(supportedCultures[0])
                .AddSupportedCultures(supportedCultures)
                .AddSupportedUICultures(supportedCultures);

              localizationOptions.RequestCultureProviders.Clear();
localizationOptions.RequestCultureProviders.Add(new  CustomRequestCultureProvider(localizationOptions));


            app.UseRequestLocalization(localizationOptions);

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/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();
            if (!env.IsDevelopment())
            {
                app.UseSpaStaticFiles();
            }

            app.UseAuthentication();
            app.UseRouting();
            app.UseAuthorization();
            app.UseCors(MyAllowSpecificOrigins);


            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
            });

            app.UseSpa(spa =>
            {
         // To learn more about options for serving an Angular SPA from ASP.NET Core,
         // see https://go.microsoft.com/fwlink/?linkid=864501

                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseAngularCliServer(npmScript: "start");
                }
            });

            app.UseHttpsRedirection();
        }
    }
 
using System;
using System.Text;
…
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;

public static class DiConfiguration
    {
        public static void ConfigureServices(IConfiguration configuration, IServiceCollection services)
        {
            // Configure Database services
            services.AddDbContextPool<IsisContext>(options => options.UseSqlServer(configuration.GetConnectionString("IsisDatabase")));

            services.AddScoped<IIsisUnitOfWork, IsisUnitOfWork>();
            …
            services.AddSingleton<ILocalizationMiddleware, LocalizationMiddleware>();

            services.AddControllers();
            services.AddMemoryCache();
            services.AddOptions();

            …

            services.AddLocalization(options => options.ResourcesPath = "Resources");
        }
}
  1. Define Localizationoptions (CustomRequestCultureProvider Class)

    using …;

    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Localization;
    using System;
    using System.Linq;
    using System.Threading.Tasks;

public class CustomRequestCultureProvider : IRequestCultureProvider
{
public CustomRequestCultureProvider(RequestLocalizationOptions options)
{

        }

            public Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
            {
                try
                {
                    var transmittedLanguageCode = httpContext.Request.GetTypedHeaders().AcceptLanguage;
                    string transmittedLanguageCodeString = transmittedLanguageCode.LastOrDefault().ToString();
                    return Task.FromResult(new ProviderCultureResult(transmittedLanguageCodeString));
                }
                catch (Exception)
                {
                    return Task.FromResult(new ProviderCultureResult(LanguagesDefinition.NL)); // scenario NL is the default
                }
            }
        }


3. Build up your middleware in the appropriate project (business logic)

using Microsoft.Extensions.Localization;

namespace ….Business.LocalizationService
{
    public interface ILocalizationMiddleware
    {
        public LocalizedString GetLocalizedString(string keyForResourceTable);
    }
}

using Microsoft.Extensions.Localization;
using System.Reflection;

namespace ….Business.LocalizationService
{
    public class LocalizationMiddleware : ILocalizationMiddleware
    {
        private readonly IStringLocalizer localizer;

        public LocalizationMiddleware(IStringLocalizerFactory factory)
        {
            localizer = factory.Create("SharedResource", Assembly.GetExecutingAssembly().FullName);
        }

        public LocalizedString GetLocalizedString(string keyForResourceTable) { return localizer[keyForResourceTable]; }
    }
}


4; Define (SharedResource.class optional) Resources-folder and resx-files in the same project

Resources-folder withing your project


5. Inject Middleware in business logic-class (handler)

public class SomeClass
    {        
        public AdvancedSearchResultDtoToCsvMap(ILocalizationMiddleware localizationMiddleware)
        {
            Map(m => m.Id).Ignore();
            Map(m => m.ArticleCode).Index(0).Name(localizationMiddleware.GetLocalizedString("articlecode").Value);
            Map(m => m.Fullname).Index(1).Name(localizationMiddleware.GetLocalizedString("fullname").Value);
           …
        }
    }
  1. Create HttpInterceptor in Angular (hic)

a. Custom made httpInterceptor

import { Injectable } from '@angular/core';
import {
    HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders
} from '@angular/common/http';

import { Observable } from 'rxjs';

import { CookieService } from 'ngx-cookie-service';

/** Pass untouched request through to the next request handler. */
@Injectable()
export class InterceptorHttpheaderAcceptLanguageService implements HttpInterceptor {
    currentLanguage: string;
    constructor(private cookieService: CookieService) {
        this.currentLanguage = this.cookieService.get('lang');
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
    {
        this.currentLanguage = this.cookieService.get('lang');
        const modifiedRequest = req.clone({ setHeaders: { 'Accept-Language': this.currentLanguage } });
        return next.handle(modifiedRequest);
    }
}

b. Put it into an index

/* "Barrel" of Http Interceptors */
import { HTTP_INTERCEPTORS } from '@angular/common/http';

import { JwtInterceptor } from './../../../core/helpers/jwt.interceptor';
import { ErrorInterceptor } from './../../../core/helpers/error.interceptor';
import { InterceptorHttpheaderAcceptLanguageService } from './interceptor.httpheader.accept.language.service';

/** Http interceptor providers in outside-in order */
export const httpInterceptorProviders = [
    { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: InterceptorHttpheaderAcceptLanguageService, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
];

c. Register the index

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
…


],
  bootstrap: [AppComponent],
  providers: [httpInterceptorProviders],
})
export class AppModule { }