public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddLocalization();
services.AddMvc(option => option.EnableEndpointRouting = false);
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("tr-TR")
};
options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders = new[] { new RouteDataRequestCultureProvider {
IndexOfCulture = 1,
IndexofUICulture = 1
}};
});
services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("culture", typeof(LanguageRouteConstraint));
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(locOptions.Value);
app.UseHttpsRedirection();
//SEO - jpg, css, js gibi dosyalara cache eklemek için
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
const int durationInSeconds = 60 * 60 * 24 * 365;
ctx.Context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.CacheControl]
= "public,max-age=" + durationInSeconds;
}
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "LocalizedDefault",
template: "{culture:culture}/{controller=Home}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "DefaultNonLocalized",
template: "{controller}/{action}/{id?}"
);
routes.MapRoute(
name: "default",
template: "",
defaults: new { controller = "Home", action = "RedirectToDefaultLanguage", culture = "tr" });
routes.MapRoute(
name: "error",
template: "{*catchall}",
defaults: new { controller = "Error", action = "HandleError", statusCode = 404 });
});
}
public class LanguageRouteConstraint : IRouteConstraint
{
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.ContainsKey("culture"))
return false;
var culture = values["culture"].ToString();
return culture == "en" || culture == "tr";
}
}
public class RouteDataRequestCultureProvider : RequestCultureProvider
{
public int IndexOfCulture;
public int IndexofUICulture;
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext == null)
throw new ArgumentNullException(nameof(httpContext));
string culture = null;
string uiCulture = null;
var twoLetterCultureName = httpContext.Request.Path.Value.Split('/')[IndexOfCulture]?.ToString();
var twoLetterUICultureName = httpContext.Request.Path.Value.Split('/')[IndexofUICulture]?.ToString();
if (twoLetterCultureName == "tr")
culture = "tr-TR";
else if (twoLetterCultureName == "en")
culture = "en-US";
if (twoLetterUICultureName == "tr")
uiCulture = "tr-TR";
else if (twoLetterUICultureName == "en")
uiCulture = "en-US";
if (culture == null && uiCulture == null)
return NullProviderCultureResult;
if (culture != null && uiCulture == null)
uiCulture = culture;
if (culture == null && uiCulture != null)
culture = uiCulture;
var providerResultCulture = new ProviderCultureResult(culture, uiCulture);
return Task.FromResult(providerResultCulture);
}
}
public ActionResult RedirectToDefaultLanguage()
{
return RedirectToAction("Index", new { culture = "en"});
}
[Route("{culture}")]
public IActionResult Index(string culture)
{
MainViewModel model = new MainViewModel();
ProductListViewModel m1 = new ProductListViewModel();
ProductListViewModel m2 = new ProductListViewModel();
if (culture == "tr")
{
m1.Id = 1;
m1.Name = "ürün 1 tr";
m1.SeoUrl = "urun-tr";
m2.Id = 2;
m2.Name = "ürün 2 tr";
m2.SeoUrl = "urun-tr";
}
else
{
m1.Id = 1;
m1.Name = "product 1 en";
m1.SeoUrl = "product-en";
m2.Id = 2;
m2.Name = "product 2 en";
m2.SeoUrl = "product-en";
}
model.Products.Add(m1);
model.Products.Add(m2);
return View(model);
}
[Route("{culture}/{seoTag:regex(egitim|training)}/{productUrl}-{productId:int}-{seoTag2:regex([[egitimi$|training$]])}/", Name = "productLink")]
public ActionResult ProductDetails(string culture, int productId, string productUrl)
{
return View();
}
//IN VIEW
//Everything works
@foreach (var item in Model.Products)
{
<div>
<a asp-route="productLink" asp-route-productId="@item.Id" asp-route-productUrl="@item.SeoUrl" asp-route-seoTag="@SharedLocalizer["TrainingSeoTag"]" asp-route-seoTag2="@SharedLocalizer["ProductSeoUrl"]">
@item.Name
</a>
</div>
<hr />
}
There is no asp-route-culture="*culture-code*"
, but "culture" is still carried when linking to the other page.
When you do this project with ASP.NET Core 6 MVC, the "culture" query string is not moved to the other page. I need to add parameter with asp-route-culture="*code"
.
This is the ASP.NET Core 6 MVC code:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews(option => option.EnableEndpointRouting = false);
builder.Services.AddHttpContextAccessor();
builder.Services.AddLocalization();
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("tr-TR"),
new CultureInfo("es-US")
};
options.DefaultRequestCulture = new RequestCulture(culture: "tr-TR", uiCulture: "tr-TR");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders = new[] { new Custom2RequestCultureProvider {
IndexOfCulture = 1,
IndexofUICulture = 1
}
};
});
builder.Services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("culture", typeof(LanguageRouteConstraint));
});
var app = builder.Build();
var options = app.Services.GetRequiredService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapControllerRoute(
name: "LocalizedDefault",
pattern: "{culture:culture}/{controller=Home}/{action=Index}/{id?}"
);
app.MapControllerRoute(
name: "DefaultNonLocalized",
pattern: "{controller}/{action}/{id?}"
);
app.MapControllerRoute(
name: "default",
pattern: "",
defaults: new { controller = "Home", action = "RedirectToDefaultLanguage", culture = "tr-TR" });
app.MapControllerRoute(
name: "error",
pattern: "{*catchall}",
defaults: new { controller = "Error", action = "HandleError", statusCode = 404 });
app.Run();
//***
//IN VIEW
//If I don't add asp-route-culture="en" it doesn't work
//***
<a asp-route="productLink" asp-route-productId="@item.Id" asp-route-productUrl="@item.SeoUrl" asp-route-seoTag="@SharedLocalizer["TrainingSeoTag"]" asp-route-seoTag2="@SharedLocalizer["ProductSeoUrl"]">
@item.Name
</a>
QUESTION: Why is asp-route-culture=""
not required in ASP.NET MVC 3, but required in ASP.NET Core 6 MVC ?
My ASP.NET MVC 3 project files : https://filetransfer.io/data-package/ljsHJom3#link
My ASP.NET Core 6 MVC project files: https://filetransfer.io/data-package/Go4VK5FB#link
2
Answers
In ASP.NET Core 6 MVC, localization is integrated deeply into the routing system. The
RequestLocalizationMiddleware
automatically detects the desired culture from the request and sets the current culture for that request. This allows you to have localized routes without explicitly specifying the culture in the route template.So, in your ASP.NET Core 6 MVC project, you don’t need to explicitly include
asp-route-culture="..."
in your route links. The framework automatically detects the culture from the request and applies it to the route.This is a design improvement to make localization more seamless and integrated into the framework. It simplifies the process for developers and provides a more consistent experience for users.
In your ASP.NET Core 6 MVC project, the following route configuration is handling localization:
According to document and discussion, it is designed so. In the real cases of users, it is a design to prevent route confusion, such as projects with area, though it brings some inconvenience.
So far you can get it work correctly through overriding the anchor tag helper and the url generator or add the asp-route-value like you did. Here is the document.
https://github.com/dotnet/aspnetcore/issues/16960