Changing culture only changing the language of model items - asp.net-mvc

I have implemented the culture in my Asp.net application. But what I want is to change the language of the entire page but it's changing only model items language.
CultureHelper.cs
public class CultureHelper
{
protected HttpSessionState session;
public CultureHelper(HttpSessionState httpSessionState)
{
session = httpSessionState;
}
public static int CurrentCulture
{
get
{
switch (Thread.CurrentThread.CurrentUICulture.Name)
{
case "en": return 0;
case "hi-IN": return 1;
default: return 0;
}
}
set
{
if (value == 0)
{
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
}
else if(value==1)
{
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("hi-IN");
}
else
{
Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
}
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture;
}
}
}
BaseController.cs
protected override void ExecuteCore()
{
int culture = 0;
if (this.Session == null || this.Session["CurrentCulture"] == null)
{
int.TryParse(System.Configuration.ConfigurationManager.AppSettings["Culture"], out culture);
this.Session["CurrentCulture"] = culture;
}
else
{
culture = (int)this.Session["CurrentCulture"];
}
// calling CultureHelper class properties for setting
CultureHelper.CurrentCulture = culture;
base.ExecuteCore();
}
protected override bool DisableAsyncSupport
{
get { return true; }
}
My model class
[Display(Name="Name",ResourceType =typeof(Resource))]
Language only changing for the model class properties. But I want to change the language for static/nonmodel properties too. Like I want to change button text too. I have added all contents in the resource file. How can I achieve this?

Add a resource file for every culture you want to support, e.g.
Resources.en.resx
Resources.hi-IN.resx
The framework will resolve which file to use based on the set CurrentCulture.
Resources.resx (without culture name) will be used as fallback if no culture specific file can be found.
Use the resource file to retrieve your translated strings, e.g. in your View.cshtml:
#using MyProject.Resources
<button type="submit">
#Resources.Submit #* this retrieves the translation for the key "Submit" *#
</button>

Related

MVC EditorFor to Title Case

How do you convert input value to title case in EditorFor? I know doing
#Html.EditorFor(model, new { htmlAttributes = new { #style = "text-transform:uppercase" } })
will only change the client side so I need to change it manually on server side.
I tried adding the class text-capitalize but seems no luck.
Thanks in advance.
Here are explanations to use either title case or sentence case for viewmodel's string properties which bound to EditorFor:
1) If you want to use title case, you can set it inside getter part with ToTitleCase method (change CurrentCulture to InvariantCulture depending on your requirements), as in example below:
private string _titleCase;
private System.Globalization.CultureInfo culture = System.Threading.Thread.CurrentThread.CurrentCulture;
public string TitleCaseProperty
{
get
{
if (string.IsNullOrEmpty(_titleCase))
{
return _value;
}
else
{
return culture.TextInfo.ToTitleCase(_titleCase.ToLower());
}
}
set
{
_titleCase = value;
}
}
View usage
#Html.EditorFor(model => model.TitleCaseProperty, ...)
2) If you want sentence case instead, use regular expression to find out sequences (referenced from this similar issue) and do similar way to getter part like above:
private string _sentenceCase;
private Regex rgx = new Regex(#"(^[a-z])|[?!.:,;]\s+(.)", RegexOptions.ExplicitCapture);
public string SentenceCaseProperty
{
get
{
if (string.IsNullOrEmpty(_sentenceCase))
{
return _value;
}
else
{
return rgx.Replace(_sentenceCase.ToLower(), s => s.Value.ToUpper());
}
}
set
{
_sentenceCase = value;
}
}
View usage
#Html.EditorFor(model => model.SentenceCaseProperty, ...)
Live example: .NET Fiddle Demo
I would recommend performing this conversion at the getter of this property using .ToUpper()
get {
if (string.IsNullOrEmpty(_value))
{
return _value;
}
return _value.ToUpper();
}
easier method
#Html.TextBoxFor(model.FieldName, new { #class = "uppercase" })
css:
.uppercase { text-transform:uppercase }

Changing Culture on Drop Down Selection

I have created an application where I require is to change the culture in the drop down selection.
This is my action method code.
public ActionResult SetCulture(string lang)
{
if (lang == "en")
return RedirectToAction("Index");
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(lang.Trim()); //.TextInfo.IsRightToLeft;
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(lang.Trim());
List<Agent> lstMainAgent = new List<Agent>();
List<Agent> lstAgent = db.Agents.ToList();
for (int i = 0; i < lstAgent.Count(); i++)
{
lstAgent[i].AddressCity = Resources.Resource.AddressCity;
lstAgent[i].AddressCountry = Resources.Resource.AddressCountry;
lstAgent[i].AddressPostcode =Resources.Resource.AddressPostcode;
lstAgent[i].AddressStreet = Resources.Resource.AddressStreet;
lstAgent[i].Name = Resources.Resource.Name;
lstAgent[i].PhoneNumber = Resources.Resource.PhoneNumber;
lstMainAgent.Add(lstAgent[i]);
}
return View("Index", lstMainAgent);
}
This seems to be working but I have dynamic values list whose values are not added in the resource file and I am getting blank properties values in the view. I need to print all the values in the view. How can I achieve this?
Thanks in Advance
If it isn´t in the resource file it will be blank. You could, however have a default resource file and specialized ones. If it has value you fill with the specialized if not the default.
public ActionResult SetCulture(string culture)
{
try
{
// set a default value
if (string.IsNullOrEmpty(culture))
{
culture = "en-US";
}
// set the culture with the chosen name
var cultureSet = CultureInfo.GetCultureInfo(culture);
Thread.CurrentThread.CurrentCulture =cultureSet;
Thread.CurrentThread.CurrentUICulture = cultureSet;
// set a cookie for future reference
HttpCookie cookie = new HttpCookie("culture")
{
Expires = DateTime.Now.AddMonths(3),
Value = culture
};
HttpContext.Response.Cookies.Add(cookie);
List<Agent> lstAgent = db.Agents.ToList();
foreach (Agent item in lstAgent)
{
item.AddressCity = Resources.Resource.AddressCity;
item.AddressCountry = Resources.Resource.AddressCountry;
item.AddressPostcode = Resources.Resource.AddressPostcode;
item.AddressStreet = Resources.Resource.AddressStreet;
item.Name = Resources.Resource.Name;
item.PhoneNumber = Resources.Resource.PhoneNumber;
}
return View("Index", lstAgent);
}
catch (Exception ex)
{
// if something happens set the culture automatically
Thread.CurrentThread.CurrentCulture = new CultureInfo("auto");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("auto");
}
return View("Index");
}

MVC: How to handle a simple multi-language display w/o a database

I have been tasked by my employer to build out a very simple customer satisfaction survey. The only catch is that it must be available in both English and Spanish. And, no database can be involved in handling the language. In other words, the actual words must be stored in the code, as variables. To make things simple (for the purpose of this discussion) let's just say that we need to display a single sentence, either in English or Spanish, depending on what parameter is on the end of the URL.
Given these two sentences:
"Display this sentence on the screen."
and
"Mostrar la frase en la pantalla."
I need some help finishing out the Model, the View, and the Controller. Where, how, would you store these two sentences in variables? Keep in mind there will eventually be a LOT of sentences, so the solution needs to scale.
Routing:
routes.MapRoute(
"PatientSatisfactionSurvey",
"PatientSatisfactionSurvey/{*LanguageCode}",
new { controller = "Forms", action = "PatientSatisfactionSurvey" },
namespaces: new[] { "xxx.Controllers" }
);
Model:
namespace GEDC.Models
{
public class PatientSatisfactionSurveyPage : Page
{
public List<string> LanguageOptions { get; set; }
public string LanguageCode { get; set; }
public PatientSatisfactionSurveyPage() { }
}
}
Controller:
[HttpGet]
public ActionResult PatientSatisfactionSurvey(string LangCode)
{
PatientSatisfactionSurveyPage pss = new PatientSatisfactionSurveyPage();
// Create list of available language options
pss.LanguageOptions = new List<string> { "English", "Espanol" };
// Check the incoming Language Code (LanCode) against the list off approved options
if (!string.IsNullOrEmpty(LangCode))
{
if (pss.LanguageOptions.Contains(LangCode))
{
pss.LanguageCode = LangCode;
}
else
{
// if the LanCode is not found, default to English
pss.LanguageCode = pss.LanguageOptions.First();
}
}
else
{
// if the LanCode is not provided, default to English
pss.LanguageCode = pss.LanguageOptions.First();
}
}
View:
I want to keep the logic to an absolute minimum here. As in, none. So, somewhere (in the controller, I assume?) Model.Sentence, or some such variable, will be populated with the correct version, depending on the parameter in the URL.
#Model.Sentence
Thanks in advance!!
You can easily do what you want using the ResourceFiles + LocalizationRoute + LocalizationAttribute GlobalFilter approach which I described in this guide on my blog.
Localization Route:
routes.MapRoute(
name: "DefaultLocalized",
url: "{lang}/{controller}/{action}/{id}",
constraints: new { lang = #"(\w{2})|(\w{2}-\w{2})" }, // en or en-US
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Localization Attribute:
public class LocalizationAttribute : ActionFilterAttribute
{
private string _DefaultLanguage = "en";
public LocalizationAttribute(string defaultLanguage)
{
_DefaultLanguage = defaultLanguage;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string lang = (string)filterContext.RouteData.Values["lang"] ?? _DefaultLanguage;
if (lang != _DefaultLanguage)
{
try
{
Thread.CurrentThread.CurrentCulture =
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
}
catch (Exception e)
{
throw new NotSupportedException(String.Format("ERROR: Invalid language code '{0}'.", lang));
}
}
}
}

MVC grid sorting - customise links

I am using the Sort method of the MvcContrib Grid to generate sorting links, e.g.
<%= Html.Grid(Model).AutoGenerateColumns().Sort((GridSortOptions)ViewData["sort"]) %>
I have a need to change the default controller/action that’s generated by the sort method. For example,
defaultControllerName/defaultActionName/?Column=ProductId&Direction=Ascending
would change to
customControllerName/customActionName/?Column=ProductId&Direction=Ascending
I haven't been able to find any existing methods in the MVCcontribution classes that would allow me to customise the links. I’d appreciate any pointers on how to go about altering the default links as I’m still very much a MVC/C# newbie.
That's not an easy task. You will need a custom grid renderer to achieve this and override the RenderHeaderText method:
public class MyHtmlTableGridRenderer<T> : HtmlTableGridRenderer<T> where TViewModel : class
{
protected override void RenderHeaderText(GridColumn<TViewModel> column)
{
if (IsSortingEnabled && column.Sortable)
{
// TODO: generate a custom link here based on the sorting options
string text = ...
base.RenderText(text);
}
else
{
RenderText(column.DisplayName);
}
}
}
And then specify that the grid should use this renderer:
.RenderUsing(new MyHtmlTableGridRenderer<Employee>())
I wanted to provide a complete working example:
public class SortableHtmlTableGridRenderer<T> : HtmlTableGridRenderer<T> where T : class
{
readonly string _action;
readonly string _controllerName;
public SortableHtmlTableGridRenderer(string action, string controllerName)
{
_action = action;
_controllerName = controllerName;
}
protected override void RenderHeaderText(GridColumn<T> column)
{
if (IsSortingEnabled && column.Sortable)
{
string sortColumnName = GenerateSortColumnName(column);
bool isSortedByThisColumn = GridModel.SortOptions.Column == sortColumnName;
var sortOptions = new GridSortOptions
{
Column = sortColumnName
};
if (isSortedByThisColumn)
{
sortOptions.Direction = (GridModel.SortOptions.Direction == SortDirection.Ascending)
? SortDirection.Descending
: SortDirection.Ascending;
}
else //default sort order
{
sortOptions.Direction = column.InitialDirection ?? GridModel.SortOptions.Direction;
}
var routeValues = HtmlHelper.AnonymousObjectToHtmlAttributes(new {sortOptions.Column, sortOptions.Direction });
var text = HtmlHelper.GenerateLink(Context.RequestContext, RouteTable.Routes, column.DisplayName, null, _action, _controllerName, routeValues, null);
RenderText(text);
}
else
{
RenderText(column.DisplayName);
}
}
}
Usage:
.RenderUsing(new SortableHtmlTableGridRenderer<YourModelType>("Search", "Search"))

ASP.NET MVC: How to maintain TextBox State when your ViewModel is a Collection/List/IEnumerable

I am using ASP.NET MVC 2 Beta. I can create a wizard like workflow using Steven Sanderson's technique (in his book Pro ASP.NET MVC Framework) except using Session instead of hidden form fields to preserve the data across requests. I can go back and forth between pages and maintain the values in a TextBox without any issue when my model is not a collection. An example would be a simple Person model:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
But I am unable to get this to work when I pass around an IEnumerable. In my view I am trying to run through the Model and generate a TextBox for Name and Email for each Person in the list. I can generate the form fine and I can submit the form with my values and go to Step2. But when I click the Back button in Step2 it takes me back to Step1 with an empty form. None of the fields that I previously populated are there. There must be something I am missing. Can somebody help me out?
Here is my View:
<% using (Html.BeginForm()) { %>
<% int index = 0;
foreach (var person in Model) { %>
<fieldset>
<%= Html.Hidden("persons.index", index.ToString())%>
<div>Name: <%= Html.TextBox("persons[" + index.ToString() + "].Name")%></div>
<div>Email: <%= Html.TextBox("persons[" + index.ToString() + "].Email")%></div>
</fieldset>
<% index++;
} %>
<p><input type="submit" name="btnNext" value="Next >>" /></p>
<% } %>
And here is my controller:
public class PersonListController : Controller
{
public IEnumerable<Person> persons;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
persons = (Session["persons"]
?? TempData["persons"]
?? new List<Person>()) as List<Person>;
// I've tried this with and without the prefix.
TryUpdateModel(persons, "persons");
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
Session["persons"] = persons;
if (filterContext.Result is RedirectToRouteResult)
TempData["persons"] = persons;
}
public ActionResult Step1(string btnBack, string btnNext)
{
if (btnNext != null)
return RedirectToAction("Step2");
// Setup some fake data
var personsList = new List<Person>
{
new Person { Name = "Jared", Email = "test#email.com", },
new Person { Name = "John", Email = "test2#email.com" }
};
// Populate the model with fake data the first time
// the action method is called only. This is to simulate
// pulling some data in from a DB.
if (persons == null || persons.Count() == 0)
persons = personsList;
return View(persons);
}
// Step2 is just a page that provides a back button to Step1
public ActionResult Step2(string btnBack, string btnNext)
{
if (btnBack != null)
return RedirectToAction("Step1");
return View(persons);
}
}
As far as I can tell, this is not supported in ASP.NET MVC 2 Beta, nor is it supported in ASP.NET MVC 2 RC. I dug through the MVC source code and it looks like Dictionaries are supported but not Models that are IEnumerable<> (or that contain nested IEnumerable objects) and it's inheritors like IList<>.
The issue is in the ViewDataDictionary class. Particularly, the GetPropertyValue method only provides a way to retrieve property values from dictionary properties (by calling GetIndexedPropertyValue) or simple properties by using the PropertyDescriptor.GetValue method to pull out the value.
To fix this, I created a GetCollectionPropertyValue method that handles Models that are collections (and even Models that contain nested collections). I am pasting the code here for reference. Note: I don't make any claims about elegance - in fact all the string parsing is pretty ugly, but it seems to be working. Here is the method:
// Can be used to pull out values from Models with collections and nested collections.
// E.g. Persons[0].Phones[3].AreaCode
private static ViewDataInfo GetCollectionPropertyValue(object indexableObject, string key)
{
Type enumerableType = TypeHelpers.ExtractGenericInterface(indexableObject.GetType(), typeof(IEnumerable<>));
if (enumerableType != null)
{
IList listOfModelElements = (IList)indexableObject;
int firstOpenBracketPosition = key.IndexOf('[');
int firstCloseBracketPosition = key.IndexOf(']');
string firstIndexString = key.Substring(firstOpenBracketPosition + 1, firstCloseBracketPosition - firstOpenBracketPosition - 1);
int firstIndex = 0;
bool canParse = int.TryParse(firstIndexString, out firstIndex);
object element = null;
// if the index was numeric we should be able to grab the element from the list
if (canParse)
element = listOfModelElements[firstIndex];
if (element != null)
{
int firstDotPosition = key.IndexOf('.');
int nextOpenBracketPosition = key.IndexOf('[', firstCloseBracketPosition);
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(element).Find(key.Substring(firstDotPosition + 1), true);
// If the Model has nested collections, we need to keep digging recursively
if (nextOpenBracketPosition >= 0)
{
string nextObjectName = key.Substring(firstDotPosition+1, nextOpenBracketPosition-firstDotPosition-1);
string nextKey = key.Substring(firstDotPosition + 1);
PropertyInfo property = element.GetType().GetProperty(nextObjectName);
object nestedCollection = property.GetValue(element,null);
// Recursively pull out the nested value
return GetCollectionPropertyValue(nestedCollection, nextKey);
}
else
{
return new ViewDataInfo(() => descriptor.GetValue(element))
{
Container = indexableObject,
PropertyDescriptor = descriptor
};
}
}
}
return null;
}
And here is the modified GetPropertyValue method which calls the new method:
private static ViewDataInfo GetPropertyValue(object container, string propertyName) {
// This method handles one "segment" of a complex property expression
// First, we try to evaluate the property based on its indexer
ViewDataInfo value = GetIndexedPropertyValue(container, propertyName);
if (value != null) {
return value;
}
// If the indexer didn't return anything useful, continue...
// If the container is a ViewDataDictionary then treat its Model property
// as the container instead of the ViewDataDictionary itself.
ViewDataDictionary vdd = container as ViewDataDictionary;
if (vdd != null) {
container = vdd.Model;
}
// Second, we try to evaluate the property based on the assumption
// that it is a collection of some sort (e.g. IList<>, IEnumerable<>)
value = GetCollectionPropertyValue(container, propertyName);
if (value != null)
{
return value;
}
// If the container is null, we're out of options
if (container == null) {
return null;
}
// Third, we try to use PropertyDescriptors and treat the expression as a property name
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(container).Find(propertyName, true);
if (descriptor == null) {
return null;
}
return new ViewDataInfo(() => descriptor.GetValue(container)) {
Container = container,
PropertyDescriptor = descriptor
};
}
Again, this is in the ViewDataDictionary.cs file in ASP.NET MVC 2 RC. Should I create a new issue to track this on the MVC codeplex site?

Resources