get culture info from users browsers for multi language website - asp.net-mvc

I have found this link as a guide to make a multi language website which it should run with users preferred language which they have set on their browsers.
Get CultureInfo from current visitor and setting resources based on that?
as you see it has this code to do it
// Get Browser languages.
var userLanguages = Request.UserLanguages;
CultureInfo ci;
if (userLanguages.Count() > 0)
{
try
{
ci = new CultureInfo(userLanguages[0]);
}
catch(CultureNotFoundException)
{
ci = CultureInfo.InvariantCulture;
}
}
else
{
ci = CultureInfo.InvariantCulture;
}
// Here CultureInfo should already be set to either user's preferable language
// or to InvariantCulture if user transmitted invalid culture ID
but my question is that I do not know what exactly is the duty of CultureInfo.InvariantCulture and it does not work at all in my project. it is always null.
I changed the code to this , It works fine but I am not sure about possible exceptions may it have. I really appreciate any kind of help.
here is what I have and it works completely fine but just not sure about possible exceptions. I want the default language to be "en-US"
public ActionResult Index()
{
CultureInfo ci;
var userLanguages = Request.UserLanguages;
if (userLanguages == null)
{
ci = new CultureInfo("en-US");
}
else if (userLanguages.Count() > 0)
{
try
{
ci = new CultureInfo(userLanguages[0]);
}
catch (CultureNotFoundException)
{
ci = new CultureInfo("en-US");
}
}
else
{
ci = new CultureInfo("en-US");
}
return RedirectToAction(ci.TwoLetterISOLanguageName, "Home");
}

Your code look fine, if user transmitted invalid culture ID it will use the "en-US" culture!
The CultureInfo.InvariantCulture property is used if you are formatting or parsing a string that should be parseable by a piece of software independent of the user's local settings.
The default value is CultureInfo.InstalledUICulture so the default CultureInfo is depending on the executing OS's settings.
The code bellow should work as well to set the culture:
private static bool DoesCultureExist(string cultureName)
{
return CultureInfo.GetCultures(CultureTypes.AllCultures).Any(culture => string.Equals(culture.Name, cultureName, StringComparison.CurrentCultureIgnoreCase));
}
public ActionResult Index()
{
CultureInfo ci;
var userLanguages = Request.UserLanguages;
if (DoesCultureExist(userLanguages?[0]))
{
ci = new CultureInfo(userLanguages[0]);
}
else
{
ci = new CultureInfo("en-US");
}
return RedirectToAction(ci.TwoLetterISOLanguageName, "Home");
}

Related

Outputcache 1 action, 2 views

So I have the following action which I am trying to add output caching to:
[OutputCache(CacheProfile = OutputCacheProfileNames.Hours24)]
public ActionResult ContactUs()
{
ContactUsModel model = _modelBuilder.BuildContactUsModel();
if (Request.IsAjaxRequest())
{
return Json(StringFromPartial(partialTemplate, model), JsonRequestBehavior.AllowGet);
}
else
{
return View(model);
}
}
But this seem to cache the first view that is requested - ie either the json OR the normal view.
Is there a way to get the output caching to work for both views, without having to split them out of the same action?
You beat me to the punch in answering your own question, but I thought this code may still be helpful. Since varying by user is such a common scenario, you should probably account for being able to do that and your AJAX vary. This code will allow you vary on any number of custom parameters, by appending to a single string to vary on.
public override string GetVaryByCustomString(System.Web.HttpContext context, string custom)
{
var args = custom.ToLower().Split(';');
var sb = new StringBuilder();
foreach (var arg in args)
{
switch (arg)
{
case "user":
sb.Append(User.Identity.Name);
break;
case "ajax":
if (context.Request.Headers["X-Requested-With"] != null)
{
// "XMLHttpRequest" will be appended if it's an AJAX request
sb.Append(context.Request.Headers["X-Requested-With"]);
}
break;
default:
continue;
}
}
return sb.ToString();
}
Then, you would just do something like the following if you need to vary by multiple custom params.
[OutputCache(CacheProfile = OutputCacheProfileNames.Hours24, VaryByCustom = "User;Ajax")]
Then, if you ever need additional custom vary params, you just keep adding case statements to cover those scenarios.
Thanks to the comments by REDEVI_ for pointing me in the right direction, I have been able to solve this.
I changed my output caching to:
[OutputCache(CacheProfile = OutputCacheProfileNames.Hours24, VaryByCustom = "IsAjax")]
And then in my global.asax file, I added the following override:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (context != null)
{
switch (custom)
{
case "IsAjax":
return new HttpRequestWrapper(context.Request).IsAjaxRequest() ? "IsAjax" : "IsNotAjax";
}
}
return base.GetVaryByCustomString(context, custom);
}

OutputCache attribute and VaryByCustom without parameter

I'm trying to use the OutputCache attribute to cache pages depending on the language users selected.
[OutputCache(Duration = 86400, Location = OutputCacheLocation.Client, VaryByParam = "", VaryByCustom = "lang")]
public ActionResult MyActionMethod()
{
...
}
It works fine when we are on the page and we change the language, cool!
But the thing is: when a user calls the page for the first time, there is no "lang" parameter. So the cache will be created without parameter and it won't be replace if we change the language after.
How can I manage this case, when there is no parameter?
Any help would be appreciated, thanks!
You are talking about there is not "lang" parameter, you mean, there is no "lang" custom?
In global.asax you should have something like this:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom == "lang")
{
string lang = null;
if (Request.UserLanguages != null && Request.UserLanguages.Length > 0)
{
lang = Request.UserLanguages.First().Split(new char[] { ';' }).First();
}
else
{
// Default
lang = "en-US";
}
return string.Format("lang={0}", lang.ToLower());
}
return base.GetVaryByCustomString(context, custom);
}
Then it will have the value "en-US" as default and otherwise get it from the browser in this case, or implement it using cookie.

Resources files with SignalrR

I have a project mvc 4 c# I'm using resource files for localization. I have action to change the culture
public ActionResult ChangeCulture(string lang, string returnUrl)
{
if (lang.Equals("he-IL"))
{
CultureInfo ci = new CultureInfo(lang);
ci.DateTimeFormat.ShortDatePattern = "dd/MM/yy";
ci.DateTimeFormat.LongDatePattern = "dd/MM/yy";
ci.DateTimeFormat.LongTimePattern = "HH:mm";
ci.DateTimeFormat.ShortTimePattern = "HH:mm";
ci = new CultureInfo(lang);
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
else if (lang.Equals("en-US"))
{
CultureInfo ci = new CultureInfo(lang);
ci = new CultureInfo(lang);
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
else
{
return new HttpStatusCodeResult(404);
}
return Redirect(returnUrl);
}
in the view I get the text from resource and it's working good.
<div>#(ViewRes.GlobalResource.Hello)</div>
but when I return text from SignalR hub it's always the same language. why?
SignalR does not localize your text within it's stack, you have to select the correctly localized text and return that string yourself. This should be as easy as requesting your resource from a ResourceManager and letting it select the correctly localized text for you. A really simple example of that looks something like this:
public string YourHubMethod()
{
ResourceManager resourceManager = new ResourceManager("YourNamespace.YourResourcesName",
typeof(YourHubType).Assembly);
return resourceManager.GetString("SomeResourceName");
}
Update:
Now that you've provided some more information about how you remember the current culture between requests for your web application in the comments (you use a cookie) I can give you more information on how to make this transfer over to SignalR.
You'll first want to override your Hub's OnConnected property and there you can extract the cookie value and store it in SignalR connection state like so:
public override Task OnConnected()
{
Client.Culture = Context.Request.Cookies["YourCultureCookieName"];
return base.OnConnected();
}
Once it's stored in the client state, you can now build a HubPipelineModule that looks for that state and sets the culture for each logical SignalR request:
public class CallerCulturePipelineModule : HubPipelineModule
{
protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
{
// Use the value we stored in the Culture property of the caller's state when they connected
CultureInfo callerCultureInfo = new CultureInfo(context.Hub.Context.Caller.Culture);
Thread.CurrentThread.CurrentUICulture = callerCultureInfo;
Thread.CurrentThread.CurrentCulture = callerCultureInfo;
return base.OnBeforeIncoming(context);
}
}
You then need to make sure to register the pipeline module as part of your Application_Start:
GlobalHost.HubPipeline.AddModule(new CallerCulturePipelineModule());
Finally, if the user changes their culture while they're already connected you would need to update their connection state with the newly selected value (since they're not going to reconnect).There's actually no way to modify connection state outside the hub itself, so you would need to actually get the IHubContext for your hub and update the client through an explicit callback to the client inside of your existing MVC controller's ChangeCulture method. To do this I would suggest you pass the connection ID to ChangeCulture as an optional query string variable (since the user might not be connected to your hub all the time?) and then use that to call back to the client:
string connectionId = <get connectionId from request here>;
IHubContext yourHubContext GlobalHost.GetHubContext<YourHub>();
yourHubContext.Client(connectionId).UpdateCultureState(lang);
And then on the client side you just update the state in JavaScript:
yourHubProxy.client.updateCultureState = function(lang)
{
yourHubProxy.state.culture = lang;
}

Does Glass.Mapper support links to MVC Models in Sitecore 7?

In Sitecore 7, it is now possible to create model definition items under /sitecore/layout/Models and link them to my Layouts and Renderings (as opposed to hard-coding the type + assembly on each one). This makes it much easier to manage, especially if I ever need to update my namespaces or see which layouts are using a particular model.
That said, I have hit an issue whereby Glass.Mapper appears to parse the 'Model' field as though it is a text field, when it is in fact an Internal Link field. Here is the error I am getting:
Could not load type '/sitecore/layout/Models/HomeViewModel' from
assembly 'Glass.Mapper.Sc, Version=3.0.2.8, Culture=neutral,
PublicKeyToken=null'.
Does anyone now if Glass supports linking to Models in Sitecore 7, or am I correct to assume that it's just not resolving the Internal Link field to the Model?
UPDATE: This works properly now in the latest version, none of what I said below is needed anymore, keeping for posterity's sake.
I ran into this problem myself and unfortunately it does not appear as if this is supported. I looked on the Trello for Glass and it looks like Sitecore 7 testing/support is an upcoming task although it mostly works in its current form aside from one or two issues.
That being said since it's open source it's not too much of a hassle to get it working yourself. Below is the full code for the Glass.Mapper.Sc.Pipelines.Response.GetModel class which I've modified to resolve two issues I was having
The issue you've mentioned
Extending the class to allow for automatically using the DataSource instead of the context item to generate the model. I've included a comment in the code to let you know how to disable this portion of the file, because it relies on another change elsewhere in the library (but I can provide that as well if you'd like).
Please note that I have not modified all methods in this class, just the ones that have broken for me so far. The modifications themselves are fairly straightforward (just make it look for the linked item instead of a text field). I hope this helps.
public class GetModel : GetModelProcessor
{
public GetModel()
{
ContextName = "Default";
}
public string ContextName { get; set; }
public override void Process(GetModelArgs args)
{
if (args.Result == null)
{
Rendering rendering = args.Rendering;
if (rendering.RenderingType == "Layout")
{
args.Result = GetFromItem(rendering, args);
if (args.Result == null)
{
args.Result = GetFromLayout(rendering, args);
}
}
if (args.Result == null)
{
args.Result = GetFromPropertyValue(rendering, args);
}
if (args.Result == null)
{
args.Result = GetFromField(rendering, args);
}
}
}
protected virtual object GetFromField(Rendering rendering, GetModelArgs args)
{
Item obj = ObjectExtensions.ValueOrDefault<RenderingItem, Item>(rendering.RenderingItem, (Func<RenderingItem, Item>)(i => i.InnerItem));
if (obj == null)
return (object)null;
Item model = MvcSettings.GetRegisteredObject<ItemLocator>().GetItem(obj["Model"]);
if (model == null)
return (object)null;
else
return GetObject(model["Model Type"], rendering);
}
protected virtual object GetFromPropertyValue(Rendering rendering, GetModelArgs args)
{
string model = rendering.Properties["Model"];
if (StringExtensions.IsWhiteSpaceOrNull(model))
return (object)null;
else
return GetObject(model, rendering);
}
protected virtual object GetFromLayout(Rendering rendering, GetModelArgs args)
{
string pathOrId = rendering.Properties["LayoutId"];
if (StringExtensions.IsWhiteSpaceOrNull(pathOrId))
return (object)null;
string modelItemPath = ObjectExtensions.ValueOrDefault<Item, string>(MvcSettings.GetRegisteredObject<ItemLocator>().GetItem(pathOrId), (Func<Item, string>)(i => i["Model"]));
string model = ObjectExtensions.ValueOrDefault<Item, string>(MvcSettings.GetRegisteredObject<ItemLocator>().GetItem(modelItemPath), (Func<Item, string>)(i => i["Model Type"]));
if (StringExtensions.IsWhiteSpaceOrNull(model))
return (object)null;
else
return GetObject(model, rendering);
}
protected virtual object GetFromItem(Rendering rendering, GetModelArgs args)
{
string model = ObjectExtensions.ValueOrDefault<Item, string>(rendering.Item, (Func<Item, string>)(i => i["MvcLayoutModel"]));
if (StringExtensions.IsWhiteSpaceOrNull(model))
return (object)null;
else
return GetObject(model, rendering);
}
public object GetObject(string model, Rendering rendering)
{
if (model.IsNullOrEmpty())
return null;
var type = Type.GetType(model, true);
if (type == null)
return null;
var context = Context.Contexts[ContextName];
if (context == null) throw new MapperException("Failed to find context {0}".Formatted(ContextName));
if (context.TypeConfigurations.ContainsKey(type))
{
ISitecoreContext scContext = new SitecoreContext(context);
//comment this if block out if you just need to solve the model link problem
if (rendering != null)
{
if (rendering.Item != null)
{
var dataSourceResult = scContext.GetCurrentItem(type, itemOverride: rendering.Item);
return dataSourceResult;
}
}
var result = scContext.GetCurrentItem(type);
return result;
}
return null;
}
}

Get CultureInfo from current visitor and setting resources based on that?

How can I (in ASP .NET MVC) get the CultureInfo of the current visitor (based on his/her browser languages)?
I have no idea where to start. I tried looking into the "Accept-Languages" header sent by the browser. But is that the best way of doing it?
Request.UserLanguages is the property you're looking for. Just keep in mind that this array may contain arbitrary (even non-exsitent) languages as set by request headers.
UPDATE
Example:
// Get Browser languages.
var userLanguages = Request.UserLanguages;
CultureInfo ci;
if (userLanguages.Count() > 0)
{
try
{
ci = new CultureInfo(userLanguages[0]);
}
catch(CultureNotFoundException)
{
ci = CultureInfo.InvariantCulture;
}
}
else
{
ci = CultureInfo.InvariantCulture;
}
// Here CultureInfo should already be set to either user's prefereable language
// or to InvariantCulture if user transmitted invalid culture ID
Asp.Net Core version: using RequestLocalization ie the culture is retrieved form the HTTP Request.
in Startup.cs - Configure
app.UseRequestLocalization();
Then in your Controller/Razor Page.cs
var locale = Request.HttpContext.Features.Get<IRequestCultureFeature>();
var BrowserCulture = locale.RequestCulture.UICulture.ToString();
You can use code similar to the following to get various details from your user (including languages):
MembershipUser user = Membership.GetUser(model.UserName);
string browser = HttpContext.Request.Browser.Browser;
string version = HttpContext.Request.Browser.Version;
string type = HttpContext.Request.Browser.Type;
string platform = HttpContext.Request.Browser.Platform;
string userAgent = HttpContext.Request.UserAgent;
string[] userLang = HttpContext.Request.UserLanguages
It appears Request.UserLanguages is not available in later mvc versions (Asp.net core mvc 2.0.2 didn't have it.)
I made an extension method for HTTPRequest. Use it as follows:
var requestedLanguages = Request.GetAcceptLanguageCultures();
The method will give you the cultures from the Accept-Language header in order of preference (a.k.a. "quality").
public static class HttpRequestExtensions
{
public static IList<CultureInfo> GetAcceptLanguageCultures(this HttpRequest request)
{
var requestedLanguages = request.Headers["Accept-Language"];
if (StringValues.IsNullOrEmpty(requestedLanguages) || requestedLanguages.Count == 0)
{
return null;
}
var preferredCultures = requestedLanguages.ToString().Split(',')
// Parse the header values
.Select(s => new StringSegment(s))
.Select(StringWithQualityHeaderValue.Parse)
// Ignore the "any language" rule
.Where(sv => sv.Value != "*")
// Remove duplicate rules with a lower value
.GroupBy(sv => sv.Value).Select(svg => svg.OrderByDescending(sv => sv.Quality.GetValueOrDefault(1)).First())
// Sort by preference level
.OrderByDescending(sv => sv.Quality.GetValueOrDefault(1))
.Select(sv => new CultureInfo(sv.Value.ToString()))
.ToList();
return preferredCultures;
}
}
Tested with ASP.NET Core MVC 2.0.2
It's similar to #mare's answer, but a bit more up-to-date and the q (quality) is not ignored. Also, you may want to append the CultureInfo.InvariantCulture to the end of the list, depending on your usage.
I am marking this question for myself with a star and sharing here some code that essentially turns the Request.UserLanguages into an array of CultureInfo instances for further use in your application. It is also more flexible to work with CultureInfo than just the ISO codes, because with CultureInfo you get access to all the properties of a culture (like Name, Two character language name, Native name, ...):
// Create array of CultureInfo objects
string locale = string.Empty;
CultureInfo[] cultures = new CultureInfo[Request.UserLanguages.Length + 1];
for (int ctr = Request.UserLanguages.GetLowerBound(0); ctr <= Request.UserLanguages.GetUpperBound(0);
ctr++)
{
locale = Request.UserLanguages[ctr];
if (!string.IsNullOrEmpty(locale))
{
// Remove quality specifier, if present.
if (locale.Contains(";"))
locale = locale.Substring(0, locale.IndexOf(';'));
try
{
cultures[ctr] = new CultureInfo(locale, false);
}
catch (Exception) { continue; }
}
else
{
cultures[ctr] = CultureInfo.CurrentCulture;
}
}
cultures[Request.UserLanguages.Length] = CultureInfo.InvariantCulture;
HTH
var userLanguage = CultureInfo.CurrentUICulture;

Resources