I have an ASP.NET MVC site that it's in two languages using Resources. To allow the server to present the site in the apropiate language (depending on the one that's configured in the user's browser) I put the following in my web.config:
<globalization culture="es-es" uiCulture="auto" />
How can I add a link to change the uiCulture? I want to store the selection in a cookie and if it's not present, then fall back to the browser configuration... Is it possible?
You may take a look at the following guide. It uses Session to store the current user language preference but the code could be very easily tweaked in order to use a cookie. The idea is that you will have a controller action:
public ActionResult ChangeCulture(string lang, string returnUrl)
{
var langCookie = new HttpCookie("lang", lang)
{
HttpOnly = true
};
Response.AppendCookie(langCookie);
return Redirect(returnUrl);
}
and then in Global.asax you could subscribe for the Application_AcquireRequestState event in order to set the current thread culture based on the value of the cookie:
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
var langCookie = HttpContext.Current.Request.Cookies["lang"];
if (langCookie != null)
{
var ci = new CultureInfo(langCookie.Value);
//Checking first if there is no value in session
//and set default language
//this can happen for first user's request
if (ci == null)
{
//Sets default culture to english invariant
string langName = "en";
//Try to get values from Accept lang HTTP header
if (HttpContext.Current.Request.UserLanguages != null && HttpContext.Current.Request.UserLanguages.Length != 0)
{
//Gets accepted list
langName = HttpContext.Current.Request.UserLanguages[0].Substring(0, 2);
}
langCookie = new HttpCookie("lang", langName)
{
HttpOnly = true
};
HttpContext.Current.Response.AppendCookie(langCookie);
}
//Finally setting culture for each request
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = ci;
//The line below creates issue when using default culture values for other
//cultures for ex: NumericSepratore.
//Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
}
Now this being said using cookies and session to store current language is not SEO friendly. What I prefer doing when I need a localized application is to use a special route which will contain the language:
routes.MapRoute(
"Default",
"{lang}/{controller}/{action}/{id}",
new
{
lang = "en-US",
controller = "Home",
action = "Index",
id = UrlParameter.Optional
}
);
and then prefix all my urls with the language. This provides unique urls for different languages so that robots can properly index all content. Now all that's left is to modify the Application_AcquireRequestState method so that it uses the lang token of the url and based on its value set the proper Thread.CurrentThread.CurrentUICulture and Thread.CurrentThread.CurrentCulture.
And now when you wanted to change the language you would simply generate the proper link:
#Html.ActionLink("Page index en français", "index", new { lang = "fr-FR" })
An alternative and I feel it is more flexible
protected override void ExecuteCore()
{
if (RouteData.Values["lang"] != null && !string.IsNullOrWhiteSpace(RouteData.Values["lang"].ToString()))
{
SetCulture(RouteData.Values["lang"].ToString());
}
else
{
var cookie = HttpContext.Request.Cookies["myappculture"];
if (cookie != null)
{ SetCulture(cookie.Value); }
else
{ SetCulture(HttpContext.Request.UserLanguages[0]);}
}
base.ExecuteCore();
}
public ActionResult ChangeCulture(string lang, string returnUrl)
{
SetCulture(lang);
// Little house keeping
Regex re = new Regex("^/\\w{2,3}(-\\w{2})?");
returnUrl = re.Replace(returnUrl,"/" + lang.ToLower());
return Redirect(returnUrl);
}
private void SetCulture(string lang)
{
CultureInfo ci = new CultureInfo(lang);
System.Threading.Thread.CurrentThread.CurrentUICulture = ci;
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
// Force a valid culture in the URL
RouteData.Values["lang"] = lang;
// save the location into cookie
HttpCookie _cookie = new HttpCookie("myappculture", Thread.CurrentThread.CurrentUICulture.Name);
_cookie.Expires = DateTime.Now.AddYears(1);
HttpContext.Response.SetCookie(_cookie);
}
In the view
I kept the resource in a different project as follows
If you use the App_GloabalResources to store your resx language files, all you have to do is add a drop down which changes the current thread's UI Culture and this will automatically select the right resx language file to display.
App_GloabalResources is not the right place the resources when it comes to MVC programmering. See http://buildingwebapps.blogspot.no/2012/05/right-way-to-use-resource-files-for.html
Related
I'm trying to follow some tutorial where on one place I should override GetVaryByCustomString ....
Code is following
public override string GetVaryByCustomString(HttpContext context, string arg)
{
// It seems this executes multiple times and early, so we need to extract language again from cookie.
if (arg == "culture") // culture name (e.g. "en-US") is what should vary caching
{
string cultureName = null;
// Attempt to read the culture cookie from Request
HttpCookie cultureCookie = Request.Cookies["_culture"];
if (cultureCookie != null)
cultureName = cultureCookie.Value;
else
cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
return cultureName.ToLower();// use culture name as cache key, "es", "en-us", "es-cl", etc.
}
return base.GetVaryByCustomString(context, arg);
}
Question is: Where I should put these code, in which class?
You can override it in Global.asax
I have a problem with redirection in my BaseController (MVC 3) I have two langage versions of my site polish and english (set by $.cookie plugin). If, is set the english language and user wants to go to the polish URL - I want to make a proper redirection. But, anytime I make the redirection within the OnActionExecuting method, in the ExecuteCore method, I see that the language switches for some reason - see the (*) line.
What's more, in that scenario I get the infinite redirections between OnActionExecuting and ExecuteCore methods. Why ?
public class BaseController : Controller
{
protected override void ExecuteCore()
{
if (Request.Cookies["language"] != null)
{
if (Request.Cookies["language"].Value != "pl-PL" && Request.Cookies["language"].Value != "en-US")
Request.Cookies["language"].Value = "en-US";
}
else
Request.Cookies.Add(new HttpCookie("language", "en-US"));
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(Request.Cookies["language"].Value);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
base.ExecuteCore();
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
switch (filterContext.ActionDescriptor.ActionName)
{
case "Onas":
if (Request.Cookies["language"].Value == "en-US")
{
filterContext.Result = new RedirectResult("/About", true);
return;
}
break;
case "About":
if (Request.Cookies["language"].Value == "pl-PL")
{
(*) filterContext.Result = new RedirectResult("/Onas", true); // Request.Cookies["language"].Value will be en-US inside the ExecuteCore method - why ?
return;
}
break;
}
base.OnActionExecuting(filterContext);
}
}
Check the cookies Path property. You could get infinite redirections by having en-US on /Onas url and pl-PL on /About url.
Make sure to set global cookies with jQuery plugin with { path: '/' }
I have an MVC 3 intranet application that performs windows authentication against a particular domain. I would like to render the current user's name.
in the view,
#User.Identity.Name
is set to DOMAIN\Username, what I want is their full Firstname Lastname
You can do something like this:
using (var context = new PrincipalContext(ContextType.Domain))
{
var principal = UserPrincipal.FindByIdentity(context, User.Identity.Name);
var firstName = principal.GivenName;
var lastName = principal.Surname;
}
You'll need to add a reference to the System.DirectoryServices.AccountManagement assembly.
You can add a Razor helper like so:
#helper AccountName()
{
using (var context = new PrincipalContext(ContextType.Domain))
{
var principal = UserPrincipal.FindByIdentity(context, User.Identity.Name);
#principal.GivenName #principal.Surname
}
}
If you indend on doing this from the view, rather than the controller, you need to add an assembly reference to your web.config as well:
<add assembly="System.DirectoryServices.AccountManagement" />
Add that under configuration/system.web/assemblies.
Another option, without requiring a helper... You could just declare context and principal before you need to utilize these values, and then utilize it like a standard output...
#{ // anywhere before needed in cshtml file or view
var context = new PrincipalContext(ContextType.Domain);
var principal = UserPrincipal.FindByIdentity(context, User.Identity.Name);
}
Then anywhere within the document, just call each variable as needed:
#principal.GivenName // first name
#principal.Surname // last name
If you have many controllers then using #vcsjones approach might be painfull.
Therefore I'd suggest creating extension method for TIdentity.
public static string GetFullName(this IIdentity id)
{
if (id == null) return null;
using (var context = new PrincipalContext(ContextType.Domain))
{
var userPrincipal = UserPrincipal.FindByIdentity(context, id.Name);
return userPrincipal != null ? $"{userPrincipal.GivenName} {userPrincipal.Surname}" : null;
}
}
And then you can use it in your view:
<p>Hello, #User.Identity.GetFullName()!</p>
If you've upgraded to Identity 2 and are using claims, then this kind of info would be a claim. Try creating an extension method:
public static string GetFullName(this IIdentity id)
{
var claimsIdentity = id as ClaimsIdentity;
return claimsIdentity == null
? id.Name
: string.Format("{0} {1}",
claimsIdentity.FindFirst(ClaimTypes.GivenName).Value,
claimsIdentity.FindFirst(ClaimTypes.Surname).Value);
}
Then you can use it in the view like this:
#Html.ActionLink("Hello " + User.Identity.GetFullName() + "!", "Manage", "Account", routeValues: null, htmlAttributes: new { title = "Manage" })
Add the below in your _ViewImports.cshtml page :
#using System.DirectoryServices.AccountManagement
Then, in your _Layouts.cshtml place the below :
#{
var context = new PrincipalContext(ContextType.Domain);
var principal = UserPrincipal.FindByIdentity(context, User.Identity.Name);}
Note: You can concatenate by creating additional variable ex:
var userName = #principal.Givenname + ", " + #principal.Surname;
You can not call the variable 'userName' directly, however, you can call 'userName' anywhere on the page by creating a hidden field.
I have a bilingual MVC 3 application, I use cookies and session to save "Culture" in Session_start method inside Global.aspx.cs file, but direct after it, the session is null.
This is my code:
protected void Session_Start(object sender, EventArgs e)
{
HttpCookie aCookie = Request.Cookies["MyData"];
if (aCookie == null)
{
Session["MyCulture"] = "de-DE";
aCookie = new HttpCookie("MyData");
//aCookie.Value = Convert.ToString(Session["MyCulture"]);
aCookie["MyLang"] = "de-DE";
aCookie.Expires = System.DateTime.Now.AddDays(21);
Response.Cookies.Add(aCookie);
}
else
{
string s = aCookie["MyLang"];
HttpContext.Current.Session["MyCulture"] = aCookie["MyLang"];
}
}
and second time it goes into the "else clause" because the cookie exists; inside my Filter, when it tries set the culutre, Session["MyCulture"] is null.
public void OnActionExecuting(ActionExecutingContext filterContext)
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(HttpContext.Current.Session["MyCulture"].ToString());
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture(HttpContext.Current.Session["MyCulture"].ToString());
}
Why are you using HttpContext.Current in an ASP.NET MVC application? Never use it. That's evil even in classic ASP.NET webforms applications but in ASP.NET MVC it's a disaster that takes all the fun out of this nice web framework.
Also make sure you test whether the value is present in the session before attempting to use it, as I suspect that in your case it's not HttpContext.Current.Session that is null, but HttpContext.Current.Session["MyCulture"]. So:
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var myCulture = filterContext.HttpContext.Session["MyCulture"] as string;
if (!string.IsNullOrEmpty(myCulture))
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(myCulture);
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(myCulture);
}
}
So maybe the root of your problem is that Session["MyCulture"] is not properly initialized in the Session_Start method.
Is there any good method to create route rewriting for a multilingual web application?
The URL schema should be the following
http://<Domainname>/{Language}/{Controller}/{Action}/{Id}
but URLs without the Language part should also be supported, but they should not just map to the controllers directly but generate a redirect response.
The important thing here is that the redirect should not be hard coded to a specific language but be determined based on factors like the users preferred language if possible.
Note: The process of determining the correct language is not the problem, just how to do the non static rewriting.
Thanks
I managed that with following routes;
routes.MapRoute(
"Default", // Route name
"{language}/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", language = "tr", id = UrlParameter.Optional }, // Parameter defaults
new { language = #"(tr)|(en)" }
);
I handle the culture by overriding the GetControllerInstance() method of DefaultControllerFactory. the example is below;
public class NinjectControllerFactory : DefaultControllerFactory {
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) {
//Get the {language} parameter in the RouteData
string UILanguage;
if (requestContext.RouteData.Values["language"] == null) {
UILanguage = "tr";
}
else {
UILanguage = requestContext.RouteData.Values["language"].ToString();
}
//Get the culture info of the language code
CultureInfo culture = CultureInfo.CreateSpecificCulture(UILanguage);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
return base.GetControllerInstance(requestContext, controllerType);
}
}
and register it on the global.asax;
protected void Application_Start() {
//other things here
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
}