Localization of string arrays (asp-items) - localization

I have a static class:
public static class MyDict
{
public static IEnumerable<string> fruits = new string[] { "Apple", "Banana", "Cherry" };
public static IEnumerable<string> vegetables = new string[] { "Carrot", "Tomato", "Cabbage" };
}
That is easily used from the View:
<select asp-for="Fruits" asp-items="#(new SelectList(#ListingDict.fruits ))"></select>
Now I also need a localized (French) list of fruits. I know how to inject IStringLocalizer/IHtmlLocalizer/IViewLocalizer into the view, and for simple strings I use #Localizer("Foo"). But what is the best way for handling string arrays? Do I need to get away from the static MyDict and instead inject IStringLocalizer into the MyDict instance, but then what? Localize each item of the array individuality, e.g. {_localizer("Apple"), _localizer("Banana")}?
I really like the simplicity I have now with just a static lookup class, but I don't think it works with localization. What is the best approach here? How do you store string arrays in the resources file?
Thanks

Create a single model holding your desired properties with localization values, then inject it into the view or add it as a model or add separate View Components with different contents

Related

Ho to use a custom mapper with mapstruct with nested values and conditional values

I am trying to map one object to another using mapstrut and currently facing some challenges on how to use it for some cases.
public class TargetOrderDto {
String id;
String preferedItem;
List<Item> items;
String status;
Address address;
}
public class Item {
String id;
String name;
}
public abstract class TargetOrderMapper {
#Autowired
private StatusRepository statusRepository;
#Mappings({
#Mapping(target = "id", source = "reference"),
#Mapping(target = "preferedItem", source = ""), // Here I need to loop through these values checking for a single value with a specific tag
#Mapping(target = "items", source = "items"), // List of objects to another list of different data types.
#Mapping(target = "status", source = "remoteStatus") // may need to extract a value from a repository
})
abstract OrderDto toTargetOrderDto(RemoteOrder remoteOrder);
}
// Remote Data
public class RemoteOrder {
String reference;
List<Item> items;
String remoteStatus;
}
public class RemoteItem {
String id;
String flag;
String description;
}
These are the current scenarios that I have failed to get my head around (maybe I am mapping a complex object).
preferedItem :
for this, I need to loop though the items in the order and identify the item with a specific flag. (if it matches then I take that value else I use null)
items :
I need to convert this to a list of 2 different lists; List from List, all have different mapping rules of their own.
remoteStatus :
This one is abit more tricky, I need to extract the status from remoteOrder then lookit up in the db using the statusRepository for an alternate mapped value in db.
any help is highly appreciated.
You can't do business logic with MapStruct. So keep mappings simple and define your own methods were it comes to conditional mappings in list. Note: you can write your own method and MapStruct will select it. Also, from this own implementation you can refer to MapStruct methods again.
public abstract class TargetOrderMapper {
#Autowired
private StatusRepository statusRepository;
#Mappings({
#Mapping(target = "id", source = "reference"),
#Mapping(target = "preferedItem", source = ""), // Here I need to loop through these values checking for a single value with a specific tag
#Mapping(target = "items", source = "items"), // List of objects to another list of different data types.
#Mapping(target = "status", source = "remoteStatus") // may need to extract a value from a repository
})
abstract OrderDto toTargetOrderDto(RemoteOrder remoteOrder);
protected List<Item> toItemList(List<Item> items) {
// do what ever you want..
// and call toItem during iterating.
}
protected abstract Item toItem(Item item);
}
The same goes for status. I added a FAQ entry some time ago about list (mainly about updating, but I guess the same applies here).
About lookups, you can use #MappingContext to pass down a context that contains the logic to access a DB. See here

ASP.NET MVC - Using IF Statement in my Details View

I have this in my Details View
<tr>
<td>#Html.DisplayNameFor(model => model.ACTION_STATUS):</td>
<td>#Html.DisplayFor(model => model.ACTION_STATUS)</td>
</tr>
How do I apply this:
If ACTION_STATUS == , then display Active Else If ACTION_STATUS ==0, display Inactive, Else Deleted
I suggest using Dictionary instead of if conditions. In this case may seem easy doing an if condition but that is not the right way if there are several branches.
Dictionary<int, string> data = new Dictionary<int, string>();
data.Add(1, "Active");
data.Add(0, "Inactive");
data.Add(-1, "Deleted");
ViewData["data"]=data;
In View you can get them:
#{
var Dictionary = (Dictionary<int, string>)ViewData["data"];
}
and display it as
<td>#Dictionary[model.ACTION_STATUS]</td>
I think this solution is cleaner.
Edit: To address the edit to the question.
I think the best approach, especially now that there are 3 different status options not just 2 is to do the following (as I recommended in my original answer).
1) Create a dictionary similar to Indrit Kello's answer, but make it "static data" somewhere in your project. Maybe it's loaded from a database table of action statuses. Maybe it is something that you load from a config file, or maybe it's just hard-coded in a static class called "Constants.cs" etc. Depends on the situation. If it were hard coded, you might have a class like:
public static class Constants
{
public static Dictionary<int, string> ActionStatuses = new Dictionary<int, string>(){
{ 1, "Active" },
{ 0, "Inactive"},
//etc. Additional statuses go here. No comma on the final entry...
};
}
2) Add a new property to your view model to decode the display text for this status. You can decorate this with attributes etc. as you can normal VM properties so you can continue to use #Html.DisplayFor etc,
public class MyViewModel
{
// Other properties etc...
public string ActionStatusDisplayText { get { return Constants.ActionStatuses[this.ACTION_STATUS]}; }
}
3) Use this new property in your view.
<td>#Html.DisplayFor(model => model.ActionStatusDisplayText)</td>
4) If you have to do this lookup in multiple different view models, you might want to consider moving the lookup code into the Constants class so that you can share the same code with multiple places. Especially true if you need to implement hte case where as tatus might not be in the dictionary for some reason (value = -99 for somereason etc.)

NewtonSoft json Contract Resolver with MVC 4.0 Web Api not producing the output as expected

I am trying to create a conditional ContractResolver so that I can control the serialization differently depending on the web request/controller action.
For example in my User Controller I want to serialize all properties of my User but some of the related objects I might only serialize the primitive types. But if I went to my company controller I want to serialize all the properties of the company but maybe only the primitive ones of the user (because of this I don't want to use dataannotations or shouldserialize functions.
So looking at the custom ContractResolver page i created my own.
http://james.newtonking.com/projects/json/help/index.html?topic=html/ContractResolver.htm
It looks like this
public class IgnoreListContractResolver : DefaultContractResolver
{
private readonly Dictionary<string, List<string>> IgnoreList;
public IgnoreListContractResolver(Dictionary<string, List<string>> i)
{
IgnoreList = i;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();
if(IgnoreList.ContainsKey(type.Name))
{
properties.RemoveAll(x => IgnoreList[type.Name].Contains(x.PropertyName));
}
return properties;
}
}
And then in my web api controller action for GetUsers i do this
public dynamic GetUsers()
{
List<User> Users = db.Users.ToList();
List<string> RoleList = new List<string>();
RoleList.Add("UsersInRole");
List<string> CompanyList = new List<string>();
CompanyList.Add("CompanyAccesses");
CompanyList.Add("ArchivedMemberships");
CompanyList.Add("AddCodes");
Dictionary<string, List<string>> IgnoreList = new Dictionary<string, List<string>>();
IgnoreList.Add("Role", RoleList);
IgnoreList.Add("Company", CompanyList);
GlobalConfiguration
.Configuration
.Formatters.JsonFormatter
.SerializerSettings
.ContractResolver = new IgnoreListContractResolver(IgnoreList);
return new { List = Users, Status = "Success" };
}
So when debugging this I see my contract resolver run and it returns the correct properties but the Json returned to the browser still contains entries for the properties I removed from the list.
Any ideas what I am missing or how I can step into the Json serialization step in webapi controllers.
*UPDATE**
I should add that this is in an MVC4 project that has both MVC controllers and webapi controllers. The User, Company, and Role objects are objects (created by code first) that get loaded from EF5. The controller in question is a web api controller. Not sure why this matters but I tried this in a clean WebApi project (and without EF5) instead of an MVC project and it worked as expected. Does that help identify where the problem might be?
Thanks
*UPDATE 2**
In the same MVC4 project I created an extension method for the Object class which is called ToJson. It uses Newtonsoft.Json.JsonSerializer to serialize my entities. Its this simple.
public static string ToJson(this object o, Dictionary<string, List<string>> IgnoreList)
{
JsonSerializer js = JsonSerializer.Create(new Newtonsoft.Json.JsonSerializerSettings()
{
Formatting = Formatting.Indented,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
ContractResolver = new IgnoreListContractResolver(IgnoreList),
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
js.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
var jw = new StringWriter();
js.Serialize(jw, o);
return jw.ToString();
}
And then in an MVC action i create a json string like this.
model.jsonUserList = db.Users.ToList().ToJson(IgnoreList);
Where the ignore list is created exactly like my previous post. Again I see the contract resolver run and correctly limit the properties list but the output json string still contains everything (including the properties I removed from the list). Does this help? I must be doing something wrong and now it seems like it isn't the MVC or web api framework. Could this have anything to do with EF interactions/ proxies /etc. Any ideas would be much appreciated.
Thanks
*UPDATE 3***
Process of elimination and a little more thorough debugging made me realize that EF 5 dynamic proxies were messing up my serialization and ContractResolver check for the type name match. So here is my updated IgnoreListContractResolver. At this point I am just looking for opinions on better ways or if I am doing something terrible. I know this is jumping through a lot of hoops just to use my EF objects directly instead of DTOs but in the end I am finding this solution is really flexible.
public class IgnoreListContractResolver : CamelCasePropertyNamesContractResolver
{
private readonly Dictionary<string, List<string>> IgnoreList;
public IgnoreListContractResolver(Dictionary<string, List<string>> i)
{
IgnoreList = i;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();
string typename = type.Name;
if(type.FullName.Contains("System.Data.Entity.DynamicProxies.")) {
typename = type.FullName.Replace("System.Data.Entity.DynamicProxies.", "");
typename = typename.Remove(typename.IndexOf('_'));
}
if (IgnoreList.ContainsKey(typename))
{
//remove anything in the ignore list and ignore case because we are using camel case for json
properties.RemoveAll(x => IgnoreList[typename].Contains(x.PropertyName, StringComparer.CurrentCultureIgnoreCase));
}
return properties;
}
}
I think it might help if you used Type instead of string for the ignore list's key type. So you can avoid naming issues (multiple types with the same name in different namespaces) and you can make use of inheritance. I'm not familiar with EF5 and the proxies, but I guess that the proxy classes derive from your entity classes. So you can check Type.IsAssignableFrom() instead of just checking whether typename is a key in the ignore list.
private readonly Dictionary<Type, List<string>> IgnoreList;
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();
// look for the first dictionary entry whose key is a superclass of "type"
Type key = IgnoreList.Keys.FirstOrDefault(k => k.IsAssignableFrom(type));
if (key != null)
{
//remove anything in the ignore list and ignore case because we are using camel case for json
properties.RemoveAll(x => IgnoreList[key].Contains(x.PropertyName, StringComparer.CurrentCultureIgnoreCase));
}
return properties;
}
Then the ignore list must be created like this (I also used the short syntax for creating the list and dictionary):
var CompanyList = new List<string> {
"CompanyAccesses",
"ArchivedMemberships",
"AddCodes"
};
var IgnoreList = new Dictionary<Type, List<string>> {
// I just replaced "Company" with typeof(Company) here:
{ typeof(Company), CompanyList }
};
Be aware that, if you use my code above, adding typeof(object) as the first key to the ignore list will cause this entry to be matched every time, and none of your other entries will ever be used! This happens because a variable of type object is assignable from every other type.

How do I populate a dropdownlist with enum values?

I have an enum for one of the properties of my view-model. I want to display a drop-down list that contains all the values of the enum. I can get this to work with the following code.
What I'm wondering is whether there is a simple way to convert from an enum to an IEnumerable? I can do it manually as in the following example, but when I add a new enum value the code breaks. I imagine that I can do it via reflection as per this example, but but are there other ways to do this?
public enum Currencies
{
CAD, USD, EUR
}
public ViewModel
{
[Required]
public Currencies SelectedCurrency {get; set;}
public SelectList Currencies
{
List<Currencies> c = new List<Currencies>();
c.Add(Currencies.CAD);
c.Add(Currencies.USD);
c.Add(Currencies.EUR);
return new SelectList(c);
}
}
I'm using a helper that i found here to populate my SelectLists with a generic enum type, i did a little modification to add the selected value though, here's how it looks like :
public static SelectList ToSelectList<T>(this T enumeration, string selected)
{
var source = Enum.GetValues(typeof(T));
var items = new Dictionary<object, string>();
var displayAttributeType = typeof(DisplayAttribute);
foreach (var value in source)
{
FieldInfo field = value.GetType().GetField(value.ToString());
DisplayAttribute attrs = (DisplayAttribute)field.
GetCustomAttributes(displayAttributeType, false).FirstOrDefault()
items.Add(value, attrs != null ? attrs.GetName() : value.ToString());
}
return new SelectList(items, "Key", "Value", selected);
}
The nice thing about it is that it reads the DisplayAttribute as the title rather than the enum name. (if your enums contain spaces or you need localization then it makes your life much easier)
So you will need to add the Display attirubete to your enums like this :
public enum User_Status
{
[Display(Name = "Waiting Activation")]
Pending, // User Account Is Pending. Can Login / Can't participate
[Display(Name = "Activated" )]
Active, // User Account Is Active. Can Logon
[Display(Name = "Disabled" )]
Disabled, // User Account Is Diabled. Can't Login
}
and this is how you use them in your views.
<%: Html.DropDownList("ChangeStatus" , ListExtensions.ToSelectList(Model.statusType, user.Status))%>
Model.statusType is just an enum object of type User_Status.
That's it , no more SelectLists in your ViewModels. In my example I'm refrencing an enum in my ViewModel but you can Refrence the enum type directly in your view though. I'm just doing it to make everything clean and nice.
Hope that was helpful.
Look at Enum.GetNames(typeof(Currencies))
I am very late on this one but I just found a really cool way to do this with one line of code, if you are happy to add the Unconstrained Melody NuGet package (a nice, small library from Jon Skeet).
This solution is better because:
It ensures (with generic type constraints) that the value really is an enum value (due to Unconstrained Melody)
It avoids unnecessary boxing (due to Unconstrained Melody)
It caches all the descriptions to avoid using reflection on every call (due to Unconstrained Melody)
It is less code than the other solutions!
So, here are the steps to get this working:
In Package Manager Console, "Install-Package UnconstrainedMelody"
Add a property on your model like so:
//Replace "YourEnum" with the type of your enum
public IEnumerable<SelectListItem> AllItems
{
get
{
return Enums.GetValues<YourEnum>().Select(enumValue => new SelectListItem { Value = enumValue.ToString(), Text = enumValue.GetDescription() });
}
}
Now that you have the List of SelectListItem exposed on your model, you can use the #Html.DropDownList or #Html.DropDownListFor using this property as the source.
So many good answers - I thought I'sd add my solution - I am building the SelectList in the view (and not in the Controller):
In my c#:
namespace ControlChart.Models
//My Enum
public enum FilterType {
[Display(Name = "Reportable")]
Reportable = 0,
[Display(Name = "Non-Reportable")]
NonReportable,
[Display(Name = "All")]
All };
//My model:
public class ChartModel {
[DisplayName("Filter")]
public FilterType Filter { get; set; }
}
In my cshtml:
#using System.ComponentModel.DataAnnotations
#using ControlChart.Models
#model ChartMode
#*..........*#
#Html.DropDownListFor(x => x.Filter,
from v in (ControlChart.Models.FilterType[])(Enum.GetValues(typeof(ControlChart.Models.FilterType)))
select new SelectListItem() {
Text = ((DisplayAttribute)(typeof(FilterType).GetField(v.ToString()).GetCustomAttributes(typeof(DisplayAttribute), false).First())).Name,
Value = v.ToString(),
Selected = v == Model.Filter
})
HTH
Maybe is too late, but i think it could be useful for people with the same problem.
I've found here that now with MVC 5 it's included an EnumDropDownListFor html helper that makes for no longer necesary the use of custom helpers or other workarounds.
In this particular case, just add this:
#Html.EnumDropDownListFor(x => x.SelectedCurrency)
and that's all!
You can also translate or change the displayed text via data annotations and resources files:
Add the following data annotations to your enum:
public enum Currencies
{
[Display(Name="Currencies_CAD", ResourceType=typeof(Resources.Enums)]
CAD,
[Display(Name="Currencies_USD", ResourceType=typeof(Resources.Enums)]
USD,
[Display(Name="Currencies_EUR", ResourceType=typeof(Resources.Enums)]
EUR
}
Create the corresponding resources file.

ASP.NET MVC ViewModel mapping with custom formatting

The project I'm working on has a large number of currency properties in the domain model and I'm needing for format these as $#,###.## for transmitting to and from the view. I've had a view thoughts as to different approaches which could be used. One approach could be to format the values explicitly inside the view, as in "Pattern 1" from Steve Michelotti :
<%= string.Format("{0:c}",
Model.CurrencyProperty) %>
...but this starts violating DRY principle very quickly.
The preferred approach appears to be to do the formatting during the mapping between DomainModel and a ViewModel (as per ASP.NET MVC in Action section 4.4.1 and "Pattern 3"). Using AutoMapper, this will result in some code like the following:
[TestFixture]
public class ViewModelTests
{
[Test]
public void DomainModelMapsToViewModel()
{
var domainModel = new DomainModel {CurrencyProperty = 19.95m};
var viewModel = new ViewModel(domainModel);
Assert.That(viewModel.CurrencyProperty, Is.EqualTo("$19.95"));
}
}
public class DomainModel
{
public decimal CurrencyProperty { get; set; }
}
public class ViewModel
{
///<summary>Currency Property - formatted as $#,###.##</summary>
public string CurrencyProperty { get; set; }
///<summary>Setup mapping between domain and view model</summary>
static ViewModel()
{
// map dm to vm
Mapper.CreateMap<DomainModel, ViewModel>()
.ForMember(vm => vm.CurrencyProperty, mc => mc.AddFormatter<CurrencyFormatter>());
}
/// <summary> Creates the view model from the domain model.</summary>
public ViewModel(DomainModel domainModel)
{
Mapper.Map(domainModel, this);
}
public ViewModel() { }
}
public class CurrencyFormatter : IValueFormatter
{
///<summary>Formats source value as currency</summary>
public string FormatValue(ResolutionContext context)
{
return string.Format(CultureInfo.CurrentCulture, "{0:c}", context.SourceValue);
}
}
Using IValueFormatter this way works great. Now, how to map it back from the DomainModel to ViewModel? I've tried using a custom class CurrencyResolver : ValueResolver<string,decimal>
public class CurrencyResolver : ValueResolver<string, decimal>
{
///<summary>Parses source value as currency</summary>
protected override decimal ResolveCore(string source)
{
return decimal.Parse(source, NumberStyles.Currency, CultureInfo.CurrentCulture);
}
}
And then mapped it with:
// from vm to dm
Mapper.CreateMap<ViewModel, DomainModel>()
.ForMember(dm => dm.CurrencyProperty,
mc => mc
.ResolveUsing<CurrencyResolver>()
.FromMember(vm => vm.CurrencyProperty));
Which will satisfy this test:
///<summary>DomainModel maps to ViewModel</summary>
[Test]
public void ViewModelMapsToDomainModel()
{
var viewModel = new ViewModel {CurrencyProperty = "$19.95"};
var domainModel = new DomainModel();
Mapper.Map(viewModel, domainModel);
Assert.That(domainModel.CurrencyProperty, Is.EqualTo(19.95m));
}
... But I'm feeling that I shouldn't need to explicitly define which property it is being mapped from with FromMember after doing ResolveUsing since the properties have the same name - is there a better way to define this mapping? As I mentioned, there are a good number of properties with currency values that will need to be mapped in this fashion.
That being said - is there a way I could have these mappings automatically resolved by defining some rule globally? The ViewModel properties are already decorated with DataAnnotation attributes [DataType(DataType.Currency)] for validation, so I was hoping that I could define some rule that does:
if (destinationProperty.PropertyInfo.Attributes.Has(DataType(DataType.Currency))
then Mapper.Use<CurrencyFormatter>()
if (sourceProperty.PropertyInfo.Attributes.Has(DataType(DataType.Currency))
then Mapper.Use<CurrencyResolver>()
... so that I can minimize the amount of boilerplate setup for each of the object types.
I'm also interested in hearing of any alternate strategies for accomplishing custom formatting to-and-from the View.
From ASP.NET MVC in Action:
At first we might be tempted to pass
this simple object straight to the
view, but the DateTime? properties
[in the Model] will cause problems.
For instance, we need to choose a
formatting for them such as
ToShortDateString() or ToString(). The
view would be forced to do null
checking to keep the screen from
blowing up when the properties are
null. Views are difficult to unit
test, so we want to keep them as thin
as possible. Because the output of a
view is a string passed to the
response stream, we’ll only use
objects that are stringfriendly; that
is, objects that will never fail when
ToString() is called on them. The
ConferenceForm view model object is an
example of this. Notice in listing
4.14 that all of the properties are strings. We’ll have the dates properly
formatted before this view model
object is placed in view data. This
way, the view need not consider the
object, and it can format the
information properly.
Have you considered using an extension method to format money?
public static string ToMoney( this decimal source )
{
return string.Format( "{0:c}", source );
}
<%= Model.CurrencyProperty.ToMoney() %>
Since this is clearly a view-related (not model-related) issue, I'd try to keep it in the view if at all possible. This basically moves it to an extension method on decimal, but the usage is in the view. You could also do an HtmlHelper extension:
public static string FormatMoney( this HtmlHelper helper, decimal amount )
{
return string.Format( "{0:c}", amount );
}
<%= Html.FormatMoney( Model.CurrencyProperty ) %>
If you liked that style better. It is somewhat more View-related as it's an HtmlHelper extension.
Have you considered putting a DisplayFormat on your ViewModel? That is what I use and it's quick and simple.
ViewModel :
[DisplayFormat(DataFormatString = "{0:c}", ApplyFormatInEditMode = true)]
public decimal CurrencyProperty { get; set; }
View :
#Html.DisplayFor(m => m.CurrencyProperty)
A custom TypeConverter is what you're looking for:
Mapper.CreateMap<string, decimal>().ConvertUsing<MoneyToDecimalConverter>();
Then create the converter:
public class MoneyToDecimalConverter : TypeConverter<string, decimal>
{
protected override decimal ConvertCore(string source)
{
// magic here to convert from string to decimal
}
}

Resources