1

Is it possible to obtain the value from (lets say a string property) of a class from a custom attribute?

For example:

public class test
{
    [EncodeHTML]
    public string body { get; set; }

    public int id { get; set; }
}

I would want the custom attribute EncodeHTML to be able to obtain the value of the setting value of the "body" property.

I know this can be achieved via the following:

public string body 
{ 
get; 
set {
    value = HttpUtility.HTMLEncode(this);
}

But was wondering if this could be isolated for re-use across many class properties.

Here is a plain example of the custom attribute:

    [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
    sealed class EncodeHTMLAttribute : Attribute
    {
        public EncodeHTMLAttribute()
        {

        }
    }
Jake Dev
  • 19
  • 6
  • 1
    Not really, attributes belong to the type, not the instance. To do something like this, you would probably have to look into aspect-oriented programming (AOP) like Fody, or do IL weaving yourself (not recommended). – Ron Beyer Dec 30 '19 at 21:23
  • @RonBeyer fair enough, I was looking to get around the [AllowHtml] or [ValidateInput(false)] attributes for model binding to the controller. – Jake Dev Dec 30 '19 at 21:40
  • The constructor of an attribute runs before there are any instances of the class, so to obtain the value of a property makes no sense. That being said, an attribute can contain handlers that fire at runtime (this is how an [ActionFilter attribute can access the instance of the controller](https://stackoverflow.com/questions/1171359/asp-net-mvc-access-a-controller-property-in-an-actionfilter)) but you have to set up some code to look for the attribute and invoke the handler when each instance is created. – John Wu Dec 30 '19 at 22:05

2 Answers2

2

It is

 var attr = typeof(test).GetProperty("body").GetCustomAttribute<EncodeHTMLAttribute>()

and you can access whatever you want.

Holger
  • 2,446
  • 1
  • 14
  • 13
  • Although not applicable in the scenario I was looking for this is useful to get the value of the attribute itself. – Jake Dev Dec 30 '19 at 21:39
  • I thought you know how to access a value of a property of a given instance. It's attr.Name for example. Your attribute class does not define any custom properties. You should define a value property, assign a value, with [EncodeHtml("somevalue")] and then you can access it. – Holger Dec 30 '19 at 21:45
0

To answer the question directly - no, the attributes are just metadata. They have no idea that a runtime instance of the decorated type even exist.

The closest you could do with an attribute is, at runtime, right when the app starts, find all types that have properties marked with that attribute and rewrite the setters code to what you want. It can be done in theory, but is hard, crazy, irresponsible and completely not recommended. Refer to this SO question to jump into the rabbit hole.

Having that said, to solve the underlying problem, you can just make a custom wrapper on a string.

public class HtmlEncodedString
{
    public string Value { get; }

    public HtmlEncodedString(string value) => 
        Value = HttpUtility.HtmlEncode(value);

    public static implicit operator string(HtmlEncodedString htmlEncodedString) => 
        htmlEncodedString.Value;

    public static implicit operator HtmlEncodedString(string value) =>
        new HtmlEncodedString(value);
}

That's of course only a sketch. If you're using ASP.NET Core, consider implementing IHtmlContent. If you're allocating a lot of these, maybe making it a value type will decrease the pressure on the GC. Neverminding these details, you now can get reusability by just using this type instead of an attribute on a string.

public class Test
{
    public HtmlEncodedString Body { get; set; }

    public int Id { get; set; }
}

Because of the implicit operators, transition is seemless:

var test = new Test();
test.Body = "2 < 4";
string s = test.Body;
Console.WriteLine(s);
Console.WriteLine(test.Body);
> 2 &lt; 4
> 2 &lt; 4
V0ldek
  • 9,623
  • 1
  • 26
  • 57