2

I have an object with multiple properties like this :

public int num1 { get; set; }

public int num2 { get; set; }

public string str1 { get; set; }

public string str2 { get; set; }

These properties are inside a class which is dynamically generated so it will erase all CustomAttributes on them.

I tried adding

[Submit]
MyClass myObject

but it didn't spread on my object properties

Is there a way to do that dynamically inside the c# ?

Tikroz
  • 183
  • 2
  • 14

2 Answers2

0

I'm having some trouble understanding your question, but let me attempt to clarify.

I have an object with multiple attributes ... it will erase all CustomAttributes

In C# parlance, class members of the format <access> <type> <name> { get; set; } are referred to as "properties" rather than "attributes." "Attributes," on the other hand, are the C# implementation of annotations, such as the custom attributes to which you are referring.

That said, I currently understand you to mean you have an automatically generated class with multiple properties. You would like each of these properties to have their own custom attributes, but if you edit the class they are removed the next time it is generated, and you cannot get the class generator to include custom attributes.

It might be helpful to know more of the context of your class. How is it being generated, for example? If it is an Entity Framework class, the following SO question may provide some insight: Add data annotations to a class generated by entity framework. In general, is (or can you make) the generated class partial? If so, then you can still follow the approach in the above question's answer, viz. make your own partial class implementation that provides the properties' custom attributes.

For example, if your generated class looks (or can be made to look) like this:

/// <auto-generated />
public partial class MyClass
{
    public int Num1 { get; set; }

    public int Num2 { get; set; }

    public string Str1 { get; set; }

    public string Str2 { get; set; }
}

You could write the other part of the partial class with the custom annotations, like this:

/// human generated
public partial class MyClass
{
    [Submit]
    public int Num1 { get; set; }

    [Submit]
    public int Num2 { get; set; }

    [Submit]
    public string Str1 { get; set; }

    [Submit]
    public string Str2 { get; set; }
}

Again, without knowing more about your situation, I am not certain if this provides you the information you need, but I hope it at least gives you a starting point.

Edit

If the class is not partial, you might consider wrapping your generated class with a class whose wrapping properties use the custom attribute. For example,

/// human generated
public class MyClassWrapper
{
    private readonly MyClass wrapped;

    public MyClassWrapper(MyClass wrapped)
    {
        this.wrapped = wrapped;
    }

    [Submit]
    public int Num1 { get => this.wrapped.Num1; set => this.wrapped.Num1 = value; }

    [Submit]
    public int Num2 { get => this.wrapped.Num2; set => this.wrapped.Num2 = value; }

    [Submit]
    public string Str1 { get => this.wrapped.Str1; set => this.wrapped.Str1 = value; }

    [Submit]
    public string Str2 { get => this.wrapped.Str2; set => this.wrapped.Str2 = value; }
}

Edit 2

If you would rather have a more dynamic solution, at the cost of some design and runtime complexity, you might consider this SO question: How to add property-level Attribute to the TypeDescriptor at runtime?. It seems to address a similar concern --

Really, it's for MS's Application Settings that generates code, so you can't extend it in any way property-wise.

I won't duplicate Gman's explanation entirely here, but essentially this approach consists of

  1. Get the type (MyClass) or an instance of the type myObject
  2. Use TypeDescriptor.GetProvider(MyClass/myObject).GetTypeDescriptor(MyClass/myObject) to get the type or object's baseline ICustomTypeDescriptor
  3. Construct his PropertyOverridingTypeDescriptor with this baseline descriptor
  4. Iterate through MyClass/myObject's properties' definitions with TypeDescriptor.GetProperties(MyClass/myObject). Use TypeDescriptor.CreateProperty to create a new property definition based on the current property's definition, that adds the custom attribute EditorAttribute (or in your case SubmitAttribute), and use the PropertyOverridingTypeDescriptor constructed in 3. to use the new property definition.
  5. Construct his TypeDescriptorOverridingProvider with the PropertyOverridingTypeDescriptor constructed in 3.
  6. Apply the new property definitions to MyClass/myObject with TypeDescriptor.AddProvider
Bondolin
  • 2,793
  • 7
  • 34
  • 62
  • Thank you for your answer, I can't modify the way the class is generated, it's by using a tool from where I work. – Tikroz Jul 23 '19 at 14:05
  • OK. I suppose then the class is not `partial`? – Bondolin Jul 23 '19 at 14:06
  • No it's not ```partial``` – Tikroz Jul 23 '19 at 14:07
  • Your answer is the only solution I found for my problem but if i'm correct, I will have to add new properties in ```MyClassWrapper``` everytime I add a property in ```MyClass``` ? I will be waiting a little more to see if anyone have another solution to this or else I will be marking your answer as the solution – Tikroz Jul 23 '19 at 14:28
  • I have updated my answer referencing a more dynamic approach used by another SO'er facing a similar situation. – Bondolin Jul 23 '19 at 15:20
0

One more way to add attributes to a dynamically generated class is to add one more command line app to your code generation pipeline.

Here's an example how to rewrite C# code file with Microsoft.CodeAnalysis.CSharp library:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.IO;

namespace SB.Common.ContractGenerator
{
    class SubmitAttributeAdder : CSharpSyntaxRewriter
    {
       public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)=>
            node.WithAttributeLists(
                node.AttributeLists.Count == 0
                    ? node.AttributeLists.Add(SyntaxFactory.AttributeList()
                         .AddAttributes(SyntaxFactory.Attribute(
                            SyntaxFactory.ParseName("Submit")))
                        // Add some whitespace to keep the code neat.
                        .WithLeadingTrivia(node.GetLeadingTrivia())
                        .WithTrailingTrivia(SyntaxFactory.Whitespace(Environment.NewLine)))
                    : node.AttributeLists);
    }

    class Program
    {
        static void Main(string[] args)
        {
            // The file name to be injected as a command line parameter
            var tree = CSharpSyntaxTree.ParseText(
                SourceText.From(File.ReadAllText("Test.cs")));

            File.WriteAllText("Test.cs",
                new SubmitAttributeAdder().Visit(tree.GetRoot()).ToString());
        }
    }
}

First, the input C# code file is parsed as the syntax tree, and then the SubmitAttributeAdder class skims through all declared classes and their properties to amend an attribute list for each of them.

After that the modified syntax tree is saved back to the same file.

Here the app only adds the Submit attribute in case the attribute list of a property is empty, but one can easily make the logic more sophisticated - e.g. check whether there are other attributes, add corresponding using <namespace> for the Submit attribute and so on.

An example Test.cs file before running the app:

namespace MyProject
{
    class MyClass
    {
        public int num1 { get; set; }

        public int num2 { get; set; }

        public string str1 { get; set; }

        public string str2 { get; set; }
    }
}

...and after:

namespace MyProject
{
    class MyClass
    {
        [Submit]
        public int num1 { get; set; }

        [Submit]

        public int num2 { get; set; }

        [Submit]

        public string str1 { get; set; }

        [Submit]

        public string str2 { get; set; }
    }
}
stop-cran
  • 4,229
  • 2
  • 30
  • 47
  • *strokes beard* Interesting. Very interesting. So the linked answer in my Edit 2 does this automatically at run-time, but this might be better for making a design-time secondary generation tool. – Bondolin Jul 23 '19 at 15:23
  • @Bondolin yes, this is only applicable when you generate code before the compilation. For example I used this approach to amend C# code generated from protobuf. – stop-cran Jul 23 '19 at 15:36