8

I need to be able to apply DisplayAttribute to classes but its AttributeUsage doesn't allow that in the current .NET / .NET Core release. It looks like this has been remedied for .NET Core vNext, but if there's some workaround to be able to somehow ignore or override this restriction until this change makes its way into a .NET release that would be extremely helpful. The only option I can see is reimplementing the whole thing (including localization) but I don't really want to have to support and test that just to deprecate it as soon as .NET vNext comes out.

Any clever ideas/hacks?

Do AttributeUsage restrictions get verified at runtime by the CLR or are they just compile time restrictions? If they are only compile time checked then is there a clever way to change the metadata used by the compiler to "trick" it into allowing the usage or somehow modifying the system assembly so my dev machines allows the usage?

*I can't seem to edit the bounty description so just to clarify, solution for the bounty has to work for the .NET Framework, bonus points for .NET Core as well.

Mike Marynowski
  • 3,156
  • 22
  • 32
  • 2
    The full framework doesn't allow it either. There are a bunch of methods that use the attribute, they would have to be extended in a non-obvious way. Big bummer when you pick one that is going to be incompatible with what Microsoft plans to do. If they do it at all, not much of a promise. Localizing class names, meh, show us a practical need for it. – Hans Passant Jul 01 '18 at 17:03
  • @HansPassant Based on the link in my question it seems that .NET Core has the change made already, so I'm not sure what you mean. In terms of a use, it's just a nice and easy way to allow devs to annotate classes with friendly localizable names and descriptions so that they show up properly when views get auto-generated for them. – Mike Marynowski Jul 03 '18 at 01:57
  • @HansPassant What do you mean by methods needing to be extended in a non-obvious way, just out of curiosity. – Mike Marynowski Jul 03 '18 at 02:04
  • 2
    The [DisplayName](https://msdn.microsoft.com/en-us/library/system.componentmodel.displaynameattribute.aspx) attribute from System.ComponentModel can be applied to a class as an alternative solution. It works both in .Net and .Net Core. – OL. Jul 03 '18 at 17:30
  • @OL. Thanks for the suggestion. I already added support for DisplayNameAttribute and DescriptionAttribute as a crappy substitute but for various reasons it's far less than ideal. I'm guessing we are just going to give in and reimplement DisplayAttribute and call it ClassDisplayAttribute or something similar to get the functionality needed. – Mike Marynowski Jul 03 '18 at 22:28
  • In ASP.NET MVC you can create custom T4 templates that the view engine uses to generate code. In these templates you can utilize any mechanism to include localized data. Maybe you can do something similar in ASP.NET Core. See [How to Create Custom Scaffold Templates in ASP.NET MVC](https://www.credera.com/blog/technology-insights/microsoft-solutions/create-custom-scaffold-templates-asp-net-mvc/) article by Denis Stetsenko and [Custom scaffold templates in ASP.NET Core](https://stackoverflow.com/questions/38382954/custom-scaffold-templates-in-asp-net-core) discussion on StackOverflow. – Leonid Vasilev Jul 06 '18 at 09:54
  • You might be able to generate a dll which inherit's from a sealed class if you Emit the [il manually](https://stackoverflow.com/a/3862241/1938988) – johnny 5 Jul 06 '18 at 18:56

2 Answers2

1

I decompiled and added AttributeTargets.Class and recompiled. I changed the namespace to System.ComponentModel.MyDataAnnotations to avoid namespace collisions. If you need the namespace changed back or something I can send the sln.

https://drive.google.com/open?id=1KR5OJwsOtGUdOBWIxBoXuDHuq4Nw-X7d

mrmichaeldev
  • 329
  • 3
  • 11
  • Patching and referencing a .NET assembly is problematic and I would not recommend it. You can not patch it machine wide as the .NET assemblies are signed. You would also have to patch the GAC in order for it to work. And even if you reference the dll yourself you are running into problems as your patched types are not the same as the .NET framework ones. – a-ctor Jul 08 '18 at 15:33
  • 1
    Thanks - @a-ctor is right, this would be problematic for a few reasons but +1 for building a working copy of the class with the fixed attribute usage. – Mike Marynowski Jul 09 '18 at 13:19
  • I actually tried his method at first but wasn't able to get to work but it looks like he got it! Good luck! – mrmichaeldev Jul 09 '18 at 13:31
1

While you should not change an existing .NET Assembly - due to signing and the GAC (trouble awaits) it is possible to add the attribute onto an existing class after compiling and it works without problems. The AttributeUsage does not seem to be enforced at runtime.

So I created a little Fody add-in that rewrites a certain attribute to the DisplayAttribute:

First our little dummy attribute that will be rewritten through Fody:

[AttributeUsage (AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Class)]
public class DisplayPatchAttribute : Attribute
{
  public DisplayPatchAttribute()
  {
  }
}

And a little dummy program that tests whether the DisplayAttribute is applied to a test class. When ran without the Fody-addin it will always print "no" (Note that the test class uses our dummy attribute instead of the real one):

internal static class Program
{
  private static void Main (string[] args)
  {
    var attr = Attribute.GetCustomAttribute (typeof(Test), typeof(DisplayAttribute)) as DisplayAttribute;
    Console.WriteLine (attr == null ? "no" : "yes");
  }
}

[DisplayPatch]
internal class Test
{
}

And now we add a little Fody weaver that rewrites the attribute to the real one (hacky code incoming):

public class DisplayAttributeWeaver : BaseModuleWeaver
{
  public override void Execute()
  {
    var dataAnnotationAssembly = ModuleDefinition.AssemblyReferences.First (e => e.Name.Contains ("DataAnnotation"));
    var resolvedDataAnnotationAssembly = ModuleDefinition.AssemblyResolver.Resolve (dataAnnotationAssembly);
    var displayAttribute = resolvedDataAnnotationAssembly.Modules.First().GetType ("System.ComponentModel.DataAnnotations.DisplayAttribute");
    var displayAttributeConstructor = ModuleDefinition.ImportReference(displayAttribute.GetConstructors().First());

    foreach (var type in ModuleDefinition.Types)
    {
      var targetAttribute = type.CustomAttributes.FirstOrDefault (e => e.AttributeType.Name == "DisplayPatchAttribute");
      if (targetAttribute == null)
        continue;

      type.CustomAttributes.Remove (targetAttribute);

      var newAttr = new CustomAttribute (displayAttributeConstructor);
      type.CustomAttributes.Add (newAttr);
    }
  }

  public override IEnumerable<string> GetAssembliesForScanning()
  {
    yield return "mscorlib";
    yield return "System";
  }
}

It converts the DisplayPatchAttribute into a DisplayAttribute and so the program outputs "yes".

The DisplayPatchAttribute would then look like the normal DisplayAttribute and have its properties copied over to the new attribute.

Not tested for .NET Core but as Fody supports net core and the fix is on IL level it should work without problems.

a-ctor
  • 3,568
  • 27
  • 41