Localization in .NET Core API - api

I am trying to create localization Middleware in .NET Core API 2.2, I was following Microsoft's official instruction but I don't quite understand, how it works? I don't have views, so I must localize models with data annotations right?
I want to get language values through Accept-Language HTTP Header
here is my Middleware:
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddMvc()
.AddDataAnnotationsLocalization();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var supportedCultures = new[] { "en-US", "ka-Ge" };
var localizationOptions = new RequestLocalizationOptions().SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
}
Model:
public class PersonsModel
{
[Display(Name = "Name")]
public string Name { get; set; }
}
I have Resx Files in Resourses folders: PersonsController.en-Us

please edit/improve/correct/adjust
references:
https://damienbod.com/2015/10/21/asp-net-5-mvc-6-localization/
https://learn.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
No, you need to apply somewhere (in your LocalizationMiddleware) IStringLocalizer, so you can create a getFunction with LocalizedString as return type.
That way you can use localization in your application wherever you want it.
Based on the culture-information of your current thread, and how you have defined your SharedResource, you'll get the corresponding value for the key.
You can find a solution below:
Checklist
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
Define Localizationoptions (CustomRequestCultureProvider Class)
Build up your middleware in the appropriate project (business logic)
Define SharedResource.class and resx-files in the same project
Inject Middleware in business logic-class (handler)
 
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");
}
}
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
 
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);
…
}
}

Related

.net core 5.0 Outlook authorization Login Error

I have a .Net Core 5.0 project and I am trying to login to outlook application with this project. The purpose of the project is to get the calendar List, schedule work, etc. But when I try to login I get the following error. What is the reason?
My codes are below and I have ClientId and TenantId taken from Outlook account.
With my Localhost address given in the RedirectUrl part of the Outlook account.(http://localhost:5000)
Startup.cs
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Microsoft.OpenApi.Models;
using System.Threading.Tasks;
namespace EvetOutlookAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options => {
this.Configuration.GetSection("AzureAd").Bind(options);
options.Events.OnRedirectToIdentityProvider = context => {
if (context.HttpContext.Items.ContainsKey("allowRedirect"))
{
return Task.CompletedTask;
}
context.HandleResponse();
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
});
services.AddAuthorization(options => {
options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
});
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "EvetOutlookAPI", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "EvetOutlookAPI v1"));
}
app.UseCors(policyBuilder =>
policyBuilder.AllowCredentials().SetIsOriginAllowed(origin =>
true).AllowAnyHeader().WithExposedHeaders("Location"));
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
}
}
}
appsettings.json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "https://dev.azure.com/",
"ClientId": "***********",
"TenantId": "*************",
"CallbackPath": "/signin-oidc"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Controller;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace EvetOutlookAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LoginController : Controller
{
[HttpGet]
public ActionResult IsUserLoggedIn()
{
if (!this.HttpContext.User.Identity.IsAuthenticated)
{
return this.Unauthorized();
}
return this.Accepted();
}
[HttpGet("Authenticate")]
public async Task Login()
{
if (!this.HttpContext.User.Identity.IsAuthenticated)
{
this.HttpContext.Items.Add("allowRedirect", true);
await this.HttpContext.ChallengeAsync();
return;
}
this.HttpContext.Response.Redirect("http://localhost:5000");
}
}
}
Maybe the reason is cookies were not being set as secure.
By default, when the OIDC middleware middle generates its correlation cookie (and nonce) cookies, it sets the "SameSite" property to "None". Try using SameSiteMode.Lax.
Another way if you're using Chrome against localhost, you may have run into a change in Chrome cookie-handling behavior.
To verify, navigate to chrome://flags/ and change "Cookies without SameSite must be secure" to "Disabled".

Why "IApplicationBuilder" couldn't find definition for "UseGraphQL"?

I am new to GraphQL. Whenever I am running my project it shows
server is not reachable
I have crosschecked project files and I guess the issue is with the Startup.cs file. I am trying to use app.UseGraphQL in Configure function but the IDE could not suggest me a correct library for it.
This is my code:
using GraphQLMutationBasicCRUD.IService;
using GraphQLMutationBasicCRUD.Service;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using HotChocolate;
using GraphQLMutationBasicCRUD.GraphQL;
using HotChocolate.AspNetCore;
using HotChocolate.AspNetCore.Playground;
using Microsoft.AspNetCore.Http;
namespace GraphQLMutationBasicCRUD
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IGroupService, GroupService>();
services.AddSingleton<IStudentService, StudentService>();
services.AddGraphQL(x=> SchemaBuilder.New()
.AddServices(x)
.AddType<GroupType>()
.AddType<StudentType>()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.Create()
);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UsePlayground(new PlaygroundOptions
{
QueryPath = "/api",
Path = "/Playground"
});
}
app.UseGraphQL("/ api");
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
});
}
}
}
For full code:
GitHub
Can you modify your code like this.
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
});
endpoints.MapGraphQL();
});
I don't think you need app.UseGraphQL("/api");
You can browse your GraphQL client - https://localhost:5001/graphql/

Getting page not found trying to navigate to my asp.net core 5.0 web service

I create a new ASP.NET Core Razor Project using .Net 5.0, I have created a simple Web service API page which is below. The issue is when I run it and then in the browser I call http://localhost:50050/api/test I get "HTTP ERROR 404*" meaning my web service cannot be found. I have a similar application just done in older version of .net core and that works just fine. Does anyone know what I am doing wrong?
using Microsoft.AspNetCore.Mvc;
namespace WebApplication2.Api
{
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
public int GetTest()
{
return 21;
}
[HttpGet("{id}")]
public int GetTest([FromRoute] int id)
{
return 22;
}
}
}
The start up page is shown below:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebApplication2
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
}
Below is my project layout
You only have an endpoint to your RazorPages, not with your Controllers.
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
what you need to do is to register your Controllers in the Startup ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
then add those Controllers in your Endpoint Middleware like so:
public void Configure(IApplicationBuilder app){
// other middlewares
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});
}

URL Globalization in asp.net core 5

I am about to change my asp.net core Globalization by URL.
Just like Microsoft official site:
https://www.microsoft.com/zh-cn/ is for the Chinese version.
https://www.microsoft.com/en-us/ is for the English version.
What I want to do is something different:
https://www.aaa.com/zh-hans/index.html is for the Chinese version.
https://www.aaa.com/en/index.html is for the English version.
As you see above, I need to add a .html suffix also.
I found some tutorials on google.
Here is my code:
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddMvc().AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix,
options => options.ResourcesPath = "Resources").AddSessionStateTempDataProvider();
}
public void Configure(IApplicationBuilder app)
{
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();
}
app.UseRouting();
app.UseStaticFiles();
var SupportedCultures = new List<CultureInfo> {
new CultureInfo("en"),
new CultureInfo("zh-Hans"),
new CultureInfo("zh-Hant")
};
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en"),
SupportedCultures = SupportedCultures,
SupportedUICultures = SupportedCultures,
};
app.UseRequestLocalization(options);
var requestProvider = new RouteDataRequestCultureProvider();
options.RequestCultureProviders.Insert(0, requestProvider);
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "default", pattern: "{culture}/{controller=Home}/{action=Index}.html", defaults: new { culture = "en", controller = "Home", action = "Index" });
});
}
However, after the website ran, it reports an error:
An unhandled exception occurred while processing the request.
AmbiguousMatchException: The request matched multiple endpoints. Matches:
Sample.Controllers.HomeController.Index (Sample)
Sample.Controllers.HomeController.Error (Sample)
Sample.Controllers.HomeController.Privacy (Sample)
Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState)
It seems that's because I added a .html suffix in MapControllerRoute.
I am not quite sure whether my code is right for most of the tutorial is for asp.net core 2 or even for asp.net core 1. Most of the code now not work in asp.net core 5 anymore.
What's wrong with my code? Thank you.
HomeController:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Sample.Models;
namespace Sample.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

Why does my URL get processed before I press Enter?

I'm working through Adam Freeman's book "Pro ASP.Net Core 3" (8th edition). I am running an example of a page that uses information in a database. The Configure() method in my Startup class includes an endpoint for "/sum/{count:1000000000}". The base URL is http://localhost:5000. In Google Chrome, I type "http://localhost:5000/sum", and as soon as I type the "m", the web page is requested. If I want to get the calculation for 10 by requesting http://localhost:5000/sum/10, the page for 1000000000 is requested first. I can imagine that in a real-world application, that could end up being a problem. How do I avoid that?
Here's my startup file:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Platform.Services;
using Microsoft.EntityFrameworkCore;
using Platform.Models;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Hosting;
namespace Platform {
public class Startup {
public Startup(IConfiguration config) {
Configuration = config;
}
private IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services) {
services.AddDistributedSqlServerCache(opts =>
{
opts.ConnectionString
= Configuration["ConnectionStrings:CacheConnection"];
opts.SchemaName = "dbo";
opts.TableName = "DataCache";
});
services.AddResponseCaching();
services.AddSingleton<IResponseFormatter, HtmlResponseFormatter>();
services.AddDbContext<CalculationContext>(opts =>
{
opts.UseSqlServer(Configuration["ConnectionStrings:CalcConnection"]);
});
services.AddTransient<SeedData>();
}
public void Configure(IApplicationBuilder app,
IHostApplicationLifetime lifetime, IWebHostEnvironment env,
SeedData seedData) {
app.UseDeveloperExceptionPage();
app.UseResponseCaching();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints => {
endpoints.MapEndpoint<SumEndpoint>("/sum/{count:int=1000000000}");
endpoints.MapGet("/", async context => {
await context.Response.WriteAsync("Hello World!");
});
});
bool cmdLineInit = (Configuration["INITDB"] ?? "false") == "true";
if (env.IsDevelopment() || cmdLineInit) {
seedData.SeedDatabase();
if (cmdLineInit) {
lifetime.StopApplication();
}
}
}
}
}