I have an application that is translated and all properties in my POCO's are marked like this
[Display(ResourceType = typeof(Resources), Name = "Name")]
public string Name { get; set; }
At runtime I want to reflect on the property and get it's resource value, like this.
EdmProperty prop = entityType.Properties.SingleOrDefault(x => x.Name.Equals("Name", StringComparison.CurrentCultureIgnoreCase));
At this point I want to be able to get the Name annotation from this EdmProperty, and that's where am stuck. I have tried to look at the source file for the EdmProperty and I can't seem to find the answer.
try this code
if(prop.Documentation != null && !prop.Documentation.IsEmpty)
{
var displayName = prop.Documentation.Summary;
}
Related
Introduction
I have a class, which has properties localized through data annotations by a resource file, like this:
[Display(Name = nameof(ResxFile.SomeProperty), ResourceType = typeof(ResxFile)]
public string SomeProperty { get; set; }
Where ResxFile is a .resx file, and I'm using Name = nameof(ResxFile.SomeProperty) to get the name property of the resource file row (to make it strongly typed), and ResourceType = typeof(ResxFile) to indicate which is the resource file to use.
In my ResxFile, for the previous example, I would have something like:
Name | Value
------------------------------------------
SomeProperty | Some property localized
And in this way, for example, I can bind my class to a grid, and the column names will be localized according to the content of the resource file.
Question
I'm working with a kind of dynamic mapping, where I use the property names of my classes, and in general I get them with something like this: string propertyName = typeof(MyClassName).GetProperty(myPropertyName).Name
In this case, what I need, is the localized name assigned to that property, according to the resource file. To be more clear: string localizedPropertyName = typeof(MyClassName).GetProperty(myPropertyName).SomeMagic(); where localizedPropertyName would be "Some property localized"
I've been looking in CustomAttributes, but I only could get display name attributes, and some types, and that lands me in another job, which is invoke the resource file to get the value of a name.
I'm using .Net Framework 4.7.
Thanks in advance!
Finally, I found a solution on my own.
The problem
Then, letting a clear context, what we have is just a class (from which we can extract its type), and a PropertyName on a string, and what we want is the the localized DisplayName of that property of that class, according to a Resource File assigned on its decoration.
Let's suppose some elements to start. We have the class MyClass, which has a property called MyProperty, and which will be localized with the resource file MyResx:
public class MyClass
{
private string myProperty;
[Display(Name = nameof(MyResx.MyProperty), ResourceType = typeof(MyResx))]
public string MyProperty
{
get { return myProperty; }
set { myProperty = value; }
}
}
The resource file MyResx, has some localized string for the name MyProperty, and will look like this:
The solution
// We start with the class type, and the property name on a string
Type classType = typeof(MyClass);
string nameOfTheProperty = "MyProperty";
/* Now we get the MemberInfo of our property, wich allow us to get the
* property metadata, where is the information we are looking for. */
MemberInfo propertyMetadata = classType.GetProperty(nameOfTheProperty);
/* The decorations we used, are "Custom Attributes". Now we get those
* attributes from our property metadata: */
var customAttributes = CustomAttributeData.GetCustomAttributes(propertyMetadata).FirstOrDefault();
/* If we pay attention to our decoration, we defined "Name = nameof(MyResx.MyProperty)"
* and "ResourceType = typeof(MyResx))", so, what we are looking for from our custom
* attribures are those members, Name and ResourceType: */
var customAttributeName = customAttributes.NamedArguments.FirstOrDefault(n => n.MemberName == "Name");
var name = (customAttributeName != null) ? (string)customAttributeName.TypedValue.Value : null;
var customAttributeResourceType = customAttributes.NamedArguments.FirstOrDefault(n => n.MemberName == "ResourceType");
var resourceType = (customAttributeResourceType != null) ? (Type)customAttributeResourceType.TypedValue.Value : null;
/* Now, having the resource file from the decoration, we just create an instance to
* use it: */
var decorationResx = new ComponentResourceManager(resourceType);
// And finally, from our resource file, we get our localized display name
string localizedAttribute = decorationResx.GetString(name);
Extra
I got a lot of important information from the Microsoft reference about the NamedArguments, here: https://learn.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata.namedarguments?view=netcore-3.1
Hopefully this helps you as in the past I have used this method to translate keys in a database. This does not cover the pulling out data from the resource file, but you can either declare [Display] attribute on a property and use the full name as the key or give a static string as the key to use later in the meta data provider.
Add your own meta data providor
public class MyMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes,
Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var metadata = new ModelMetadata(this, containerType, modelAccessor, modelType, propertyName);
//Do what ever you want here to translate either by the property name or the display attribute key
if (propertyName != null)
{
var displayAttribute = attributes.OfType<DisplayAttribute>().FirstOrDefault();
if (displayAttribute != null)
{
//Translate using the key you provided before however you like
metadata.DisplayName = TranslateFunction(displayAttribute.Name);
}
}
return metadata;
}
}
add the translation key to the prop
[Display(Name = "ResourceKey")]
public string Something { get; set; }
Add this to application start up
protected void Application_Start(object sender, EventArgs e)
{
ModelMetadataProviders.Current = new MyMetadataProvider();
}
I'm developing ASP.NET MVC appliation. I've found Fluent Validation great validation tool and it works, but with my current architecture it has one drawback. The validator does not care about Metadata. I'm using Metadata on seperate class for clarity.
Model
[MetadataType(typeof(DocumentEditMetadata))]
[Validator(typeof(DocumentValidator))]
public class DocumentEditModel
{
public string DocumentNumber { get; set; }
(etc...)
}
Metadata Model
public class DocumentEditMetadata
{
[Required]
[StringLength(50)]
[Display(ResourceType = typeof(Label), Name = "DocumentNumber")]
public string DocumentNumber { get; set; }
(etc...)
}
Can anyone point a solution? I need data annotations for localization of labels (hence the DisplayAttribute).
Think you need to write your own Display name resolver for fluent validation (guess this should be placed in your global.asax).
Caution
This solution is only trying to resolve the display name.
Your other "validation" attributes (Required, StringLength) should no more be used, as you will manage that with FluentValidation.
ValidatorOptions.DisplayNameResolver = (type, memberInfo, expression) =>
{
//this will get in this case, "DocumentNumber", the property name.
//If we don't find anything in metadata / resource, that what will be displayed in the error message.
var displayName = memberInfo.Name;
//we try to find a corresponding Metadata type
var metadataType = type.GetCustomAttribute<MetadataTypeAttribute>();
if (metadataType != null)
{
var metadata = metadataType.MetadataClassType;
//we try to find a corresponding property in the metadata type
var correspondingProperty = metadata.GetProperty(memberInfo.Name);
if (correspondingProperty != null)
{
//we try to find a display attribute for the property in the metadata type
var displayAttribute = correspondingProperty.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute != null)
{
//finally we got it, try to resolve the name !
displayName = displayAttribute.GetName();
}
}
}
return displayName ;
};
Personal point of view
By the way, if you just use Metadata classes for clarity, don't use them !
It may be a solution if you have no choice (when entity classes are generated from an edmx and you really want to manage the display names this way), but I would really avoid them if it's not necessary.
public class CreateHireViewModel
{
[Display(Name = nameof(CreateHireViewModel.Title), ResourceType = typeof(Resource.HireResource.Hire))]
public string Title { get; set; }
}
public class CreateHireViewModelValidator : AbstractValidator<CreateHireViewModel>
{
public CreateHireViewModelValidator(IStringLocalizer<Resource.HireResource.Hire> l)
{
RuleFor(x => x.Title).NotEmpty().WithName(l[nameof(CreateHireViewModel.Title)]);
RuleFor(x => x.Title).Length(3, 50).WithName(l[nameof(CreateHireViewModel.Title)]);
}
}
I am currently writing an api for a custom application my company is writing. Part of this involves getting published content out in JSON format. When I try serializing ipublishedcontent directly it obviously attempts to serialize all of the umbraco data and relations that I simply don't need (in fact it fails with a stack overflow). Is there a way to get just the custom properties from an item of content without specifying the fields?
I am using webapi and passing it objects to serialize itself and I'm using a dynamic to manually specify the fields. The Product type which I'm initially selecting into is from modelsbuilder. My code currently looks a little like this:
public object Get(string keywords = "")
{
// Get Data from Umbraco
var allProducts = Umbraco.TypedContent(1100).Children.Select(x => new Product(x));
if (keywords != "")
{
allProducts = allProducts.Where(x => x.Name.Contains(keywords));
}
return allProducts.Select(x => new
{
id = x.Id,
name = x.Name,
price = x.Price
});
}
It seems to me that there should be a simple way to do this without having to create a dynamic with just the fields I want but I can't work it out. I just don't want to have to change my code every time the document type in umbraco changes!
You can use Ditto to map your data into an object.
Create an object with properties that match the alias's of your fields (case insensitive)
public class Product{
public int id {get;set;}
public string name {get;set;}
public string price {get;set;}
}
Then map a single or collection of IPublishedContent objects using .As
return allProducts.As<Product>();
You can use the UmbracoProperty attribute to specify the alias too if it is different than you need for your json or use the JsonProperty attribute to change the name on serialize.
Take a look at the code in the MemberListView - it does a similar thing while retrieving Members without knowing in advance what the properties on the MemberType will be:
https://github.com/robertjf/umbMemberListView/blob/master/MemberListView/Models/MemberListItem.cs
For example:
[DataContract(Name = "content", Namespace = "")]
public class MemberListItem
{
// The following properties are "known" - common to all IPublishedContent
[DataMember(Name = "id")]
public int Id { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "contentType")]
public IContentType ContentType { get; set; }
// This one contains a list of all other custom properties.
private Dictionary<string, string> properties;
[DataMember(Name = "properties")]
public IDictionary<string, string> Properties
{
get
{
if (properties == null)
properties = new Dictionary<string, string>();
return properties;
}
}
}
MemberListView converts to this from a list of SearchResult using AutoMapper, but you could just as easily map it from IPublishedContent.
I have the following POCO class in my app -
public class Course
{
public String Title { get; set; }
public String Description { get; set; }
}
But the Course collection in mongodb has some other fields also including those. I am trying to get data as follows-
var server = MongoServer.Create(connectionString);
var db = _server.GetDatabase("dbName");
db.GetCollection("users");
var cursor = Photos.FindAs<DocType>(Query.EQ("age", 33));
cursor.SetFields(Fields.Include("a", "b"));
var items = cursor.ToList();
I have got that code from this post in stackoverflow.
But it throws an exception-
"Element '_id' does not match any field or property of class"
I don't want '_id' field in my POCO. Any help?
_id is included in Fields by default.
You can exclude it by using something like:
cursor.SetFields(Fields.Exclude("_id"))
I am writing some code that will send an email with details of what is inside the properties of a class.
Instead of hard coding the rows with the properties, I thought it was best to do this via reflection
var builder = new StringBuilder();
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.GetValue(obj, null) != null)
{
builder.AppendLine("<tr>");
builder.AppendLine("<td>");
builder.AppendLine("<b> " + property.Name + " </b>");
builder.AppendLine("</td>");
builder.AppendLine("<td>");
builder.AppendLine(property.GetValue(obj, null).ToString());
builder.AppendLine("</td>");
builder.AppendLine("</tr>");
}
}
Which also helps leave out all the properties that hasn't been set which again helps to reduce code.
However property.Name quite rightly outputs the name of the property in its current form
public string PropertyA { get; set; }
So the Email would look like
PropertyA : 123
Which doesnt look friendly to the user. So is there a way I can change the property name to display something different?
I have tried
[DisplayName("Property A")]
public string PropertyA { get; set; }
which should look like in the email:
Property A : 123
But to no prevail.... Is there anything out there to help on the road of the logic I am going down?
Thanks
You need to find the attribute and extract the Name value:
var displayNameAttribute = property.GetCustomAttributes
(typeof(DisplayNameAttribute), false)
.FirstOrDefault() as DisplayNameAttribute;
string displayName = displayNameAttribute != null
? displayNameAttribute.DisplayName
: property.Name;
You need to get DisplayNameAttribute of your property and then get it's Name:
var attribute = property.GetCustomAttribute<DisplayNameAttribute>();
if(attribute != null)
{
var displayName = attribute.Name;
}