what cases in which ABP can automatically localize the enums if we follow enum localization conventions - enums

i try to localized enum property in DTO ABP Localization Documentation Say
We prefer some conventions for specific text types;
Add Menu: prefix for menu items.
Use Enum:: naming convention to localize the enum members. When you do it like that, ABP can automatically localize the enums in some proper cases.
ABP Documentation
but i follow this convention and value not localized return enum value
my question : is there automatic way to localize enum or what cases that ABP automatically localize the enums

CarType.cs: (enum)
public enum CarType
{
Sedan,
Hatchback,
Coupe
}
en.json: (localization file)
{
"culture": "en",
"texts": {
"Enum:CarType:0": "Sedan Car",
"Enum:CarType:1": "Hatchback Car",
"Enum:CarType:2": "Coupe Car"
}
}
index.js: (javascript usage)
var l = abp.localization.getResource("MyProject");
var localizationKey = "Enum:CarType:" + changeType;
var localized = l(localizationKey);
index.cshtml: (Razor page usage)
var statuses = (from CarType ct in Enum.GetValues(typeof(ArticleContentSource))
select new {Id = (int)ct, Name=L[$"Enum:ContentSource:{(int)ct }"].Value })
.ToList();
When ABP Tag Helpers used in MVC projects it's automatically localized via convention like "Enum:EnumName:Value": "localized text"
CarCreateModel.cs: (Model)
public class CarCreateModel
{
[Required]
[InputInfoText("Choose car type?")]
public CarType MyCarType { get; set; }
}
index.cshtml: (MVC page)
<abp-select asp-for="#Model.CarCreateModel.MyCarType"/>

I looked in the actual code and I believe the documentation is, at least in part, incorrect.
Here is the framework's code:
$"Enum:{enumType.Name}.{memberName}",
$"{enumType.Name}.{memberName}",
memberName
So, I think the correct json file format is this:
{
"culture": "en",
"texts": {
"Enum:CarType.Sedan": "Sedan Car",
"Enum:CarType.Hatchback": "Hatchback Car",
"Enum:CarType.Coupe": "Coupe Car"
}
}

Related

Understanding metadata in aspnet core

I got redirected from here: https://github.com/aspnet/AspNetCore/issues/11963
I'm in the process of converting a solution over from .Net 4.6 and i'm looking at metadata.
In the old solution I had a custom implementation of the data annotations metadata provider which I had extended like this ....
public class ApiMetadataProvider : DataAnnotationsModelMetadataProvider, IDisposable
{
public IResourceProvider ResourceProvider { get; }
public ICoreDataContext CoreDb { get; }
public ApiMetadataProvider(IResourceProvider resourceProvider, ICoreDataContext core)
{
ResourceProvider = resourceProvider;
CoreDb = core;
}
protected override ModelMetadata CreateMetadata(
IEnumerable<Attribute> attributes,
Type containerType,
Func<object> modelAccessor,
Type modelType,
string propertyName)
{
ModelMetadata modelMetadata = base.CreateMetadata(
attributes,
containerType,
modelAccessor,
modelType,
propertyName);
Type serverType = (modelType == typeof(string))
? typeof(string)
: modelType.ImplementsGenericInterface(typeof(IEnumerable<>)) ?? modelType;
if (serverType.IsGenericType && serverType.Name.StartsWith("Nullable") && typeof(Nullable<>).MakeGenericType(serverType.GenericTypeArguments) == serverType) { serverType = serverType.GenericTypeArguments[0]; }
modelMetadata.AdditionalValues.Add("ServerType", serverType.AssemblyQualifiedName);
SetTemplateHint(modelMetadata);
SetCustomAttributes(attributes, modelMetadata, modelType, propertyName);
SetResourceStrings(modelMetadata);
return modelMetadata;
}
....
}
... the key thing here is that I pull the base copy of the model meta for the given type and then manipulate it in my own custom ways (some of which is shown in the sample above).
I cut the rest out because there's quite a bit of it.
The net result is that from my own base generic controller I had an action that looked like this ...
protected MetadataContainer GetMetadataForType(Type type)
{
return new MetadataContainer(MetaProvider.GetMetadataForType(null, type));
}
Controllers would then often make decisions based on this.
I am looking to reproduce this behaviour, the key thing being the ability to get a customised version of the "final meta" from the stack (I gather from this: https://github.com/aspnet/Mvc/issues/2522 ... that meta is now a "chain of providers" in some fashion).
So I have a couple of questions ....
How can i add or remove / update custom "properties" / attributes in the meta information for a given type?
How can I get an instance of the meta that the stack sees as being the "final result" after all providers have been executed?
The existing solution often handed this meta information to client JS code to allow for "dynamic component construction" in the browser, is this a scenario that has any form of best practice that I can gather more advice from (perhaps you guys have a blog post or something to get me started)?
The answer was simple in the end ...
Don't bother doing any of this, build a cache of the reflection derived information and in the controller just serve it up.
This basically just means this is simple reflection code to extract the relevant type info wanted.

Can a DataAnnotation.UIHInt be used in a GET view

Can System.ComponentModel.DataAnnotations.UIHInt be used in a GET view:
Specified in application.json:
{
"ApplicationSettings": {
"ApplicationName": "My Application"
}
...
}
ApplicationSettings class:
public class ApplicationSettings
{
[UIHint("The name of the application displayed in nav bar")]
public string ApplicationName { get; set; }
}
Populated in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// retrieve the 'ApplicationSettings' section of the appsettings.json file
var applicationSettings = Configuration.GetSection("ApplicationSettings");
services.Configure<ApplicationSettings>(applicationSettings);
...
}
Added (somehow) to /Home/About:
<h3>ApplicationSettings</h3>
<div>
<dl class="dl-horizontal">
<dt>ApplicationName</dt>: <dd>#ApplicationSettings.Value.ApplicationName</dd>
<????>
...
</div>
Displayed in HTML:
ApplicationSettings
ApplicationName: My Application
The name of the application displayed in nav bar
Well, first, you're using UIHint wrong. It's intended for specifying a view that will be used by DisplayFor/EditorFor. So, in that respect, yes you can use it for display, as well, via DisplayFor.
However, what you're doing here is just describing what this property is for. That's a job for the Display attribute:
[Display(Name = "The name of the application displayed in nav bar")]
Then, you can just do:
#Html.DisplayNameFor(x => ApplicationSettings.Value.ApplicationName)
However, even this isn't technically what you want. More likely than not, you still want the display name to be something like Application Name, and this should truly be descriptive text in addition to that. The Display attribute does have a Description property you can use for that:
[Display(Name = "Application Name", Description = "The name of the application displayed in nav bar")]
However, there's no built-in way to actually display that Description, unfortunately. There's another SO answer that contains an extension you can add for that though.
public static class HtmlExtensions
{
public static IHtmlContent DescriptionFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
if (html == null)
throw new ArgumentNullException(nameof(html));
if (expression == null)
throw new ArgumentNullException(nameof(expression));
var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, html.ViewData, html.MetadataProvider);
if (modelExplorer == null)
throw new InvalidOperationException($"Failed to get model explorer for {ExpressionHelper.GetExpressionText(expression)}");
return new HtmlString(modelExplorer.Metadata.Description);
}
}
Courtesy: Wouter Huysentruit

ASP.NET MVC Populating DropDownListFor based on DB call

I am building a simple ASP.NET MVC 3 site, using Entity Framework to build the model based on some tables in an already existing Oracle database (in other words, I used the 'database first' methodology to have my model built). I now have a simple blog type site, which I am quite familiar with as I have been learning MVC in a number of languages.
I want to change some of the auto-generated views. One piece I would like to change in particular is that I have a field in one of my tables called 'Visible'. This is simply a numeric value (0 or 1) indicating whether or not a display application should use the row as display data. Currently, I have the simple text field that is auto-generated:
<div class="editor-field">
#Html.EditorFor(model => model.VISIBLE)
#Html.ValidationMessageFor(model => model.VISIBLE)
</div>
What I would like to do is replace this with a drop down box with string values like True and False. The application should display any entry with a 0 as false and vice versa. If a user wants to flip the toggle, the drop down should allow them to do that and understand to make the numeric update when clicking submit. How can this be done?
I have seen countless examples where the drop-down was going to be filled with more then just two values, and in those cases I understand that you can add logic to your controller that pulls all the distinct values, puts them in a list, then adds the list to the ViewBag. However, in my case with only two possible numeric values, it seems like there should be a simpler, more accepted way to do it.
Any help is greatly appreciated.
UPDATE
Following Quinton's answer, I am trying to place said code in my model. Here is my current model:
namespace CurrentActivityBlog
{
using System;
using System.Collections.Generic;
using System.Web.UI.WebControls;
public partial class TBLCURRENTACTIVITY
{
public string TITLE { get; set; }
public string DESCRIPTION { get; set; }
public System.DateTime DATETIME { get; set; }
public short VISIBLE { get; set; }
public decimal ID { get; set; }
public IEnumerable<SelectedListItem> PossibleValues { get; set; }
public TBLCURRENTACTIVITY() {
PossibleValues = new[] { new SelectListItem { Value = "0", Text = "Hidden" },
new SelectListItem { Value = "1", Text = "Visible" } };
}
}
}
I am unable to build this solution, but Visual Studio 2010 is telling me that
"the type or namespace name 'SelectedListItem' could not be found (are you missing a using directive or an assembly reference?)"
As you can see, I have
using System.Web.UI.controls
and have added the reference to System.Web. Is there anything I am forgetting, or anything I should know about (such as models generated using EF behaving differently then one might expect, etc.)?
Thanks again.
The one line solution to it would be:
#Html.DropDownList(Model.VISIBLE.ToString(), new [] {new SelectListItem { Value = "0", Text = "Hidden"}, new SelectListItem { Value = "1", Text = "Visible"}})
but you probably don't want domain logic in your view. So add the possible items to your Model (or from the controller):
public class MyModel {
public int VISIBLE { get; set; }
public IEnumerable<SelectListItem> PossibleValues { get; set; }
public MyModel() {
PossibleValues = new[] { new SelectListItem { Value = "0", Text = "Hidden" }, new SelectListItem { Value = "1", Text = "Visible" } };
}
}
and then your razor code:
#Html.DropDownList(Model.VISIBLE.ToString(), Model.PossibleValues)
Obviously "Hidden" and "Visible" descriptions can be replaced with "False" and "True" or whatever.
You could also create and Editor and Display Template for that specific field. Checkout ScottGu's blog post here, search for "UI Helper Templating Support" and you'll see how to create a editor template and how to render a specific template by name.
EDIT:
If your model is not part of your MVC project, then referencing any classes that are in the MVC assemblies would require a explicit add reference. You can avoid this though by initializing any MVC assembly types in your model from your controller, like such:
public class MyModel {
public int VISIBLE { get; set; }
public IEnumerable<SelectListItem> PossibleValues { get; set; }
}
controller action method:
public ActionResult Edit(int Id) {
...
myModelInstance. PossibleValues = new[] { new SelectListItem { Value = "0", Text = "Hidden" }, new SelectListItem { Value = "1", Text = "Visible" } };
...
Return View(myModel);
}

String.Format() with HTML attributes in a View

I have a model with some inheritance worked into it, but here's the thing, I want to apply different HTML attributes depending on which type it is. So my thought was to create a property, I'll call it DisplayString, which is used as the first argument to String.Format. DisplayString is used to allow me to do something like this:
Classes:
public class Reaction
{
public string ReactionString { get; set; }
public string DisplayString { get; set; }
public Reaction(string reactionString)
{
ReactionString = reactionString;
DisplayString = "It was {0}!"
}
public Reaction(string reactionString, string displayString)
{
ReactionString = reactionString;
DisplayString = displayString;
}
}
public class GoodReaction : Reaction
{
public GoodReaction()
{
base("awesome");
}
}
public class BadReaction : Reaction
{
public GoodReaction()
{
base("horrible");
}
}
public class AverageReaction : Reaction
{
public GoodReaction()
{
base("alright", "It kinda was {0}...");
}
}
View:
#model Reaction
#String.Format(Model.DisplayString, "<strong>" + Model.ReactionString + "</strong>");
So basically every subclass has a string I want to display, but I have varying text AND varying markup. Of course doing it this way just results with the strong tags rendered as text. So what are my options? Feel free to ask any questions needed to clarify, hopefully I got my question across properly. Thanks!
EDIT:
So I wrapped it in Html.Raw() and that did the trick, although I'm sure there is something bad about doing that and I should probably encode each piece, then add tags, then raw, or something, but for my purposes it works.
I would create a display template for each concrete class, and MVC will be smart enough to pick the right one.
E.g when you call #Html.DisplayFor(model => model.Reaction), where Reaction is declared as the base class, MVC will work out what type it is, then look for a template matching that type, if it doesn't find it, it will look for a template of the base type, then finally fallback to the default template. I do this a lot in my application.
Sounds like you should also make the Reaction class abstract, and the ctor's protected.

Making it easier to populate DropDownLists

In my MVC3 application I have a couple of create forms, and they all have one thing in common, and that is a DropDownList for selecting the language.
Two days ago I learnt that it was not a good idea to send whole entities (or list of them) to the Views. It is better to send properties via ViewModels.
So in my ViewModel, to take care of the DropDownList I have the following:
// Properties for Language DropDownList
[Required(ErrorMessage = "Language is Required")]
public int SelectedLanguageId { get; set; }
public IEnumerable<SelectListItem> Languages { get; set; }
I am still learning Web Development, and I'm not 100% sure that the above is a good idea, I just copied it from this tutorial.
Then what the tutorial author does is something like this:
// GET: Post/Create
public ActionResult Create()
{
var vm= new NewPostVM();
var langs = languagesRepository.Languages.ToList();
vm.Languages = langs.ToSelectListItems(-1);
if (langs.Count() == 0)
return RedirectToAction("Index", "Language");
return View("Create", viewModel);
}
The thing is I do not have a [ ToSelectListItems ] method to call ! so I am guessing he has put in place some sort of Extention method on his Domain (plus I have no idea what the negative one is for).
In any case, how can I populate a repeating (commonly used) DropDownList?
public ActionResult Create()
{
var vm= new NewPostVM();
var langs = languagesRepository.Languages.ToList();
vm.Languages = langs; //assign the languages list to the model property
if (langs.Count() == 0)
return RedirectToAction("Index", "Language");
return View("Create", vm); // pass the model that you have created
}
I think you can load the NewPostVM.Languages this way:
var vm = new SelectList(languagesRepository.Languages.ToList(), "idLang", "Name");
The first parameter of the SelectList constructor just need to be a IEnumerable "
OK so after about an hour in chat with 3nigma, we figured it out. I'll share it with you fellow coders.
I had a Solution with 2 projects. One holding my Domain and the other my MVC3 application. In the Domain project create a folder called [ Extensions ] (make sure you don't spell it Extentions like I did :D) and add a class naming it something related to the Entity in Question. I named mined [ LanguageEntityExtension ]
In there a followed this tutorial, and got my code to look like:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.WebPages.Html;
using PostssDomain.Entities;
namespace PostsDomain.Extensions
{
public static class LanguageEntityExtension
{
public static IEnumerable<SelectListItem> ToSelectListItems<T>(this IList<T> langeuageEntities) where T : Language
{
return ToSelectListItems((IEnumerator<Language>)langeuageEntities.GetEnumerator());
}
public static IEnumerable<SelectListItem> ToSelectListItems(this IEnumerator<Language> langeuageEntities)
{
var items = new HashSet<SelectListItem>();
while (langeuageEntities.MoveNext())
{
var item = new SelectListItem();
var entity = langeuageEntities.Current;
item.Value = entity.Id.ToString();
item.Text = entity.Name.ToString();
items.Add(item);
}
return items;
}
}
}
A couple of things here, make sure you do not forget the words [ static ], and also make sure you are referencing (using statement) the library [ System.Web.WebPages.Html; ] and not [ System.Web.Mvc; ]. You might have to add a Reference to [ System.Web.WebPages.dll ] manually by right-clicking on References folder and adding it that way.
Now then in my controller, I added the relevant using statement [ using PostsDomain.Extensions; ] and then I was able to do the following:
var langs = languagesRepository.Languages.ToList();
vm.Languages = langs.ToSelectListItems();
Where as before having the Extension method I was not. So that is that. Then in the View where you are displaying the DropDownListFor, have it in the following format:
#Html.DropDownListFor(x => x.SelectedLanguageId, new SelectList(Model.Languages, "value", "text"))
Note the "Value" and "Text".
That's it. All is working great.
Thanks once again 3nigma for the help.

Resources