Change custom attribute's parameter at runtime - c#

I need change attribute's parameter during runtime. I simplified my problem to simple example.
Attribute class:
[AttributeUsage(AttributeTargets.Property)]
public class MyAttribute : Attribute
{
public string Name { get; set; }
}
Simple entity which has decorated properties with attributes:
public class MyEntity
{
[MyAttribute(Name="OldValue1")]
public string Data1{ get; set; }
[MyAttribute(Name = "OldValue2")]
public string Data2 { get; set; }
}
I created instance of class MyEntity. I can change value of object's properties, but I can't change value of attribute’s property Name on object entity. Is it possible?
Value of property on object entity I can change with this part of code:
entityProp.SetValue(entity,"NewData",null);
but I don't how change value of attribute's property Name on object entity
This does not work:
attProp.SetValue(attribute,"NewData",null);
Value of property Name is still original.
Here is all test code.
[TestMethod]
public void Test()
{
var entity = new MyEntity
{
Data1 = "OldData",
Data2 = "OldData"
};
PropertyInfo[] entityProps = entity.GetType().GetProperties();
foreach (var entityProp in entityProps)
{
var attribute = Attribute.GetCustomAttribute(entityProp, typeof (MyAttribute)) as MyAttribute;
if (attribute != null)
{
//get attribute's property NAME
PropertyInfo attProp= attribute.GetType().GetProperty("Name");
//get entity property value
var propertyValue = entityProp.GetValue(entity, null);
//get attribute’s property NAME value
var atributeNameValue = attProp.GetValue(entity, null);
TestContext.WriteLine(string.Format("property name:{0} property value: {1} : atribute name value: {2}\n",
entityProp.Name, propertyValue, atributeNameValue));
//change values
entityProp.SetValue(entity,"NewData",null);
//how can I change value of property Name on object entity ?
attProp.SetValue(attribute,"NewData",null);
}
}
TestContext.WriteLine(string.Format("After change\n"));
foreach (var entityProp in entityProps)
{
var attribute = Attribute.GetCustomAttribute(entityProp, typeof(MyAttribute)) as MyAttribute;
if (attribute != null)
{
PropertyInfo attProp = attribute.GetType().GetProperty("Name");
var propertyValue = entityProp.GetValue(entity, null);
var atributeNameValue = attProp.GetValue(entity, null);
TestContext.WriteLine(string.Format("property name:{0} property value: {1} : atribute name value: {2}\n",
entityProp.Name, propertyValue, atributeNameValue));
}
}
}
EDITED: I delete original post and added very simple clear sample. Sorry

You cannot change attributes at runtime. They are embedded into the metadata of the assembly. Your method is changing the internal state of a particular instance; but when you load the attribute again, you are getting a different instance.

This is not possible with reflection, as (as already noted) the metadata is fixed. It is, however, partly possible with TypeDescriptor, which allows adding and replacing of attributes at runtime, and providing complete alternative models (TypeDescriptionProvider, etc). This approach will not be respected by any code that uses reflection, but any code using TypeDescriptor (most typically, data-binding and other UI code) will notice the changes.
Note TypeDescriptor only really works with one of each attribute-type per typ/member; multi-instance attributes are not well supported.

Related

Get localized display name attribute from a class property which use a resource file

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();
}

Get an exception on PropertyInfo.GetCustomAttribute<T>

I'm working on the business model for my first project(please excuse me if someone finds my code lack of quality, important thing is i'm making progress). I'm stuck trying to find the reason for a bug. I'm creating a view which rely on reflection of properties and custom attributes. I get a null reference exception when i use the PropertyInfo.GetCustomAttribute for the second time on a "property's property". Why does my second call return null. As you can see I have applied the attribute on the property(_TopSchools) which i invoke method on.
public class EducationFilter : Filter
{
[FilterAttribute(FilterType.Child, "Topschools")]//I cant get this attr!
public TopSchoolFilter _TopSchool { get; set; }
}
public class TopSchoolFilter :BooleanFilter
{
}
public class Filters
{
[FilterAttribute(FilterType.Parent, "Education")] //This i can...
public EducationFilter _EducationFilter { get; set; }
public Filters(EducationFilter educationFilter)
{
this._EducationFilter = educationFilter;
}
}
public StackLayout GenerateFilterView(PropertyInfo p,TestModel vm)
{
StackLayout tempStack = new StackLayout();
**FilterAttribute filterAttr = p.GetCustomAttribute<FilterAttribute>();**//This returns the attr instance
IEnumerable<PropertyInfo> filterProperties = p.PropertyType.GetRuntimeProperties();
foreach (PropertyInfo p1 in filterProperties)
{
**FilterAttribute filterAttr1 = p1.GetCustomAttribute<FilterAttribute>();**//But not this one, i get null
If GetCustomAttribute<T>() returns null then that means the custom attribute provider (the property in this case) doesn't have an attribute of that type. If you are only interested in properties with this attribute, you can just skip over the properties without the attribute.
if (filterAttr1 == null) {
continue;
}

Change property name for client output

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;
}

asp.net mvc web api partial update with OData Patch

I am using HttpPatch to partially update an object. To get that working I am using Delta and Patch method from OData (mentioned here: What's the currently recommended way of performing partial updates with Web API?). Everything seems to be working fine but noticed that mapper is case sensitive; when the following object is passed the properties are getting updated values:
{
"Title" : "New title goes here",
"ShortDescription" : "New text goes here"
}
But when I pass the same object with lower or camel-case properties, Patch doesn't work - new value is not going through, so it looks like there is a problem with deserialisation and properties mapping, ie: "shortDescription" to "ShortDescription".
Is there a config section that will ignore case sensitivity using Patch?
FYI:
On output I have camel-case properties (following REST best practices) using the following formatter:
//formatting
JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings = jss;
//sample output
{
"title" : "First",
"shortDescription" : "First post!"
}
My model classes however are follwing C#/.NET formatting conventions:
public class Entry {
public string Title { get; set;}
public string ShortDescription { get; set;}
//rest of the code omitted
}
Short answer, No there is no config option to undo the case sensitiveness (as far as i know)
Long answer: I had the same problem as you today, and this is how i worked around it.
I found it incredibly annoying that it had to be case sensitive, thus i decided to do away with the whole oData part, since it is a huge library that we are abusing....
An example of this implementation can be found at my github github
I decided to implement my own patch method, since that is the muscle that we are actually lacking. I created the following abstract class:
public abstract class MyModel
{
public void Patch(Object u)
{
var props = from p in this.GetType().GetProperties()
let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))
where attr == null
select p;
foreach (var prop in props)
{
var val = prop.GetValue(this, null);
if (val != null)
prop.SetValue(u, val);
}
}
}
Then i make all my model classes inherit from *MyModel*. note the line where i use *let*, i will excplain that later. So now you can remove the Delta from you controller action, and just make it Entry again, as with the put method. e.g.
public IHttpActionResult PatchUser(int id, Entry newEntry)
You can still use the patch method the way you used to:
var entry = dbContext.Entries.SingleOrDefault(p => p.ID == id);
newEntry.Patch(entry);
dbContext.SaveChanges();
Now, let's get back to the line
let attr = p.GetCustomAttribute(typeof(NotPatchableAttribute))
I found it a security risk that just any property would be able to be updated with a patch request. For example, you might now want the an ID to be changeble by the patch. I created a custom attribute to decorate my properties with. the NotPatchable attribute:
public class NotPatchableAttribute : Attribute {}
You can use it just like any other attribute:
public class User : MyModel
{
[NotPatchable]
public int ID { get; set; }
[NotPatchable]
public bool Deleted { get; set; }
public string FirstName { get; set; }
}
This in this call the Deleted and ID properties cannot be changed though the patch method.
I hope this solve it for you as well. Do not hesitate to leave a comment if you have any questions.
I added a screenshot of me inspecting the props in a new mvc 5 project. As you can see the Result view is populated with the Title and ShortDescription.
It can be done quite easily with a custom contract resolver that inherits CamelCasePropertyNamesContractResolver and implementing CreateContract method that look at concrete type for delta and gets the actual property name instead of using the one that comes from json. Abstract is below:
public class DeltaContractResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
// This class special cases the JsonContract for just the Delta<T> class. All other types should function
// as usual.
if (objectType.IsGenericType &&
objectType.GetGenericTypeDefinition() == typeof(Delta<>) &&
objectType.GetGenericArguments().Length == 1)
{
var contract = CreateDynamicContract(objectType);
contract.Properties.Clear();
var underlyingContract = CreateObjectContract(objectType.GetGenericArguments()[0]);
var underlyingProperties =
underlyingContract.CreatedType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in underlyingContract.Properties)
{
property.DeclaringType = objectType;
property.ValueProvider = new DynamicObjectValueProvider()
{
PropertyName = this.ResolveName(underlyingProperties, property.PropertyName),
};
contract.Properties.Add(property);
}
return contract;
}
return base.CreateContract(objectType);
}
private string ResolveName(PropertyInfo[] properties, string propertyName)
{
var prop = properties.SingleOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
if (prop != null)
{
return prop.Name;
}
return propertyName;
}
}

SqlTableProfileProvider, custom ProfileBase class, add extra attributes to properties

I am using SqlTableProfileProvider as my profile provider and a custom class called 'ProfileCommon' inheriting from System.Web.Profile.ProfileBase. My ProfileCommon class properties that represent the columns in my profile table each get an attribute of [CustomProviderData("columnName;dbType")]. I am trying to add a new attribute to specific column and then am going to pull that info from the SqlTableProfileProvider class.
The team and I are looking to associate a foreign key (table) with our Profile table. Right now, our Profile table stores, basically, key value pairs, FirstName, LastName, Age, etc; however, we are planning to store bookmarks, links to favorite articles and what not, that will be presented in a list on our dashboard page. We like using the SqlTableProfileProvider and the ProfileCommon object I created. All our asp.net pages inherit from a BasePage and a property called Profile gets the profile common object.
It would be nice to just be able to do:
Profile.Bookmarks.Count; // to know if there are bookmarks
// to also just be able to foreach through them
foreach (Bookmark bk in Profile.Bookmarks) { ... }
Ex:
public class ProfileCommon : System.Web.Profile.ProfileBase
{
public static ProfileCommon GetProfile() { .... }
[CustomProviderData("FirstName;varchar")]
public virtual string FirstName
{
get
{
return ((string)(this.GetPropertyValue("FirstName")));
}
set
{
this.SetPropertyValue("FirstName", value);
}
}
[CustomProviderData("LastName;varchar")]
public virtual string LastName
{
get
{
return ((string)(this.GetPropertyValue("LastName")));
}
set
{
this.SetPropertyValue("LastName", value);
}
}
[CustomProviderData("OtherColumn;int")]
[TableNameData("OtherTable")]
public virtual int OtherColumn
{
get ...
set ...
}
}
// My new attribute
[AttributeUsage(AttributeTargets.Property)]
public class TableNameData : Attribute
{
private string _tableName;
public TableNameData(string tableName)
{
_tableName = tableName;
}
public string TableName
{
get
{
return _tableName;
}
}
}
// Not my implementation, but looking to enhance it.
public class SqlTableProfileProvider : ProfileProvider
{
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyValueCollection svc, string username, SqlConnection conn)
{
...
foreach (SettingsProperty prop in properties)
{
...
// in here, gets CustomProviderData
string persistenceData = prop.Attributes["CustomProviderData"] as string.
// how do i get to mine?
}
}
}
The SqlTableProfileProvider was implemented by Hao Kung. It inherits from ProfileProvider.
One of the methods GetPropertyValues returns a SettingsPropertyValueCollection. There is a private method called GetProfileDataFromTable. In there, I wish to access my custom attribute that I created.
Question: How do I access my attribute that I have specified on my property?
UPDATE: 07162011:1517, 7 days after question asked,
I did find a way to do this. The following is how I did it:
// In the default constructor add the following
public ProfileCommon()
{
// Get all properties for this class 'ProfileCommon'
PropertyInfo[] propertyInfos = typeof(ProfileCommon).GetProperties();
// The ProfileBase, base class, has a property called Properties and
// one can get to all attributes on that property but there are only
// a few attributes that ProfileBase looks for. If the developer wishes
// to use custom attributes on a property, it wont appear in the
// ProfileCommon.Properties.Attributes list.
//
// So, what are we going to do, well, we are going to come up with a hack and solution to this problem
//
foreach (SettingsProperty settingsProperty in ProfileCommon.Properties)
{
foreach (PropertyInfo propertyInfo in propertyInfos)
{
if (settingsProperty.Name == propertyInfo.Name)
{
// get all attributes from the associated property, but we are getting it from the propertyInfo variable
// which was retrieved through reflection and will list ALL attributes.
object[] attributes = propertyInfo.GetCustomAttributes(false);
for (int i = 0; i < attributes.Count(); i++)
{
Type type = attributes[i].GetType();
PropertyInfo[] attributeClassProperities = type.GetProperties();
foreach (PropertyInfo attributeClassProperty in attributeClassProperities)
{
// not intested in the TypeId property for the object
if (!attributeClassProperty.Name.Equals("TypeId"))
{
// if the settingsProperty.Attributes does not contain our key value pair, then add it.
if (settingsProperty.Attributes[attributeClassProperty.Name] == null)
{
settingsProperty.Attributes.Add(attributeClassProperty.Name, attributes[i]);
}
}
}
}
}
}
}
}
You only need to take care of implementing your ProfileCommon as you have in the example and make sure the attributes are correct.
For the specialized profile property Bookmark you would also write your own profile class just as you have in the example. There you have to implement a Count property that would query the database for the number of bookmarks of that user.
In the Profile table in the database you would have a Bookmarks column with a suitable FK type such as Guid, Int or BigInt that would be nothing more than a Foreign key to another table you could name [dbo].[Bookmarks] that would actually contain the bookmarks for each user. This Bookmarks table should have a column like UserId that would be a FK to the ASP.NET UserId.

Categories

Resources