DEV Community

Cover image for Creating Custom Attributes in C#
Anton Martyniuk
Anton Martyniuk

Posted on • Originally published at antondevtips.com on

Creating Custom Attributes in C#

Attributes provide a way to add metadata to your code.
In this blog post we'll cover the basics of what attributes are, how to set attribute properties, and how to configure where attributes can be applied.
Finally, we'll dive into a practical example to demonstrate how custom attributes can be used in the applications.

On my webite: antondevtips.com I already have blogs about C#. Subscribe as more are coming.

What is an Attribute?

Attributes in C# are a way to add declarative information to your code.
They provide metadata that can be used to control various aspects of the behavior of your program at runtime or compile-time.

Attributes can be applied to various program elements like:

  • assemblies, modules, classes, interfaces, structs, enums
  • methods, constructors, delegates, events
  • properties, fields, parameters, generic parameters, return values
  • or all of the mentioned elements

You can apply one or multiple attributes to these program elements.

You're using attributes every day when creating applications in NET.
For example, the following attributes you have seen and used a lot:

[Serializable]
public class User
{
}

[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
}
Enter fullscreen mode Exit fullscreen mode

How To Create a Custom Attribute

Attribute in C# is a class that inherits from the base Attribute class.

Let's have a look at how to create a custom attribute:

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class CustomAttribute : Attribute
{
}

[Custom]
public class MyClass
{
}
Enter fullscreen mode Exit fullscreen mode

The name of the attribute class should have an Attribute suffix.
When applied to any element, this suffix is omitted.

When creating an attribute class, you're using a built-in attribute to specify where the attribute can be applied:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)]
public class CustomAttribute : Attribute
{
}
Enter fullscreen mode Exit fullscreen mode

You can combine multiple targets by "|" operator to set where an attribute can be applied.

Inherited parameter indicates whether the custom attribute can be inherited by derived classes. The default value is true.

[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public class CustomInheritedAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class CustomNonInheritedAttribute : Attribute
{
}
Enter fullscreen mode Exit fullscreen mode

When applying inherited attribute to the ParentA class, it is inherited by a ChildA class:

[CustomInherited]
public class ParentA
{
}

public class ChildA
{
}
Enter fullscreen mode Exit fullscreen mode

Here, the ChildA class has a CustomInherited attribute.

While in the following example, when using a non-inherited attribute for the ParentB, it is not applied to a ChildB class:

[CustomNonInherited]
public class ParentB
{
}

public class ChildB
{
}
Enter fullscreen mode Exit fullscreen mode

Attribute Properties

Attribute properties can be either required (mandatory parameters) or non-required (optional parameters).
Attributes can accept arguments in the same way as methods and properties.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)]
public class CustomWithParametersAttribute : Attribute
{
    public string RequiredProperty { get; }
    public int OptionalProperty { get; set; }

    public CustomWithParametersAttribute(string requiredProperty)
    {
        RequiredProperty = requiredProperty;
    }
}
Enter fullscreen mode Exit fullscreen mode

The required properties should be defined as attribute constructor parameters.
While non-required should not be defined in the constructor.

When applying such an attribute to a class, you need to set attribute values in the order they are defined in the constructor.
Optional properties should be specified by their name:

[CustomWithParameters("some text here", OptionalProperty = 5)]
public class ExampleClass
{
}
Enter fullscreen mode Exit fullscreen mode

Practical Example of Using Attributes

Let's create a custom attribute to specify the roles allowed to access a controller method:

[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class AuthorizeRolesAttribute : Attribute
{
    public string[] Roles { get; }

    public AuthorizeRolesAttribute(params string[] roles)
    {
        Roles = roles;
    }
}
Enter fullscreen mode Exit fullscreen mode

Next, we apply the AuthorizeRolesAttribute to methods in a class, specifying the roles that are allowed to access each method:

public class AccountController
{
    [AuthorizeRoles("Admin", "Manager")]
    public void AdminOnlyAction()
    {
        Console.WriteLine("Admin or Manager can access this method.");
    }

    [AuthorizeRoles("User")]
    public void UserOnlyAction()
    {
        Console.WriteLine("Only users can access this method.");
    }

    public void PublicAction()
    {
        Console.WriteLine("Everyone can access this method.");
    }
}
Enter fullscreen mode Exit fullscreen mode

To make use of this attribute, we can use reflection to get info about what attributes are applied to a method and what properties they have:

public class RoleBasedAccessControl
{
    public void ExecuteAction(object controller, string methodName, string userRole)
    {
        var method = controller.GetType().GetMethod(methodName);
        var attribute = method?.GetCustomAttribute<AuthorizeRolesAttribute>();

        if (attribute is null || attribute.Roles.Contains(userRole))
        {
            method?.Invoke(controller, null);
        }
        else
        {
            Console.WriteLine("Access denied. User does not have the required role.");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we get a method by name from a controller class and look if AuthorizeRolesAttribute attribute is applied to the method.
If applied, we check if a user can have access to the given method.

Finally, we can test the role-based access control logic with different user roles:

var controller = new AccountController();
var accessControl = new RoleBasedAccessControl();

Console.WriteLine("Testing with Admin role:");
accessControl.ExecuteAction(controller, nameof(AccountController.AdminOnlyAction), "Admin");

Console.WriteLine("\nTesting with User role:");
accessControl.ExecuteAction(controller, nameof(AccountController.UserOnlyAction), "User");

Console.WriteLine("\nTesting with Guest role:");
accessControl.ExecuteAction(controller, nameof(AccountController.AdminOnlyAction), "Guest");

Console.WriteLine("\nTesting public method with Guest role:");
accessControl.ExecuteAction(controller, nameof(AccountController.PublicAction), "Guest");
Enter fullscreen mode Exit fullscreen mode

Output:

Testing with Admin role:
Admin or Manager can access this method.

Testing with User role:
Only users can access this method.

Testing with Guest role:
Access denied. User does not have the required role.

Testing public method with Guest role:
Everyone can access this method.
Enter fullscreen mode Exit fullscreen mode

In this example, the AuthorizeRolesAttribute is used to specify the roles allowed to access each method in the AccountController.
The RoleBasedAccessControl class enforces these restrictions by checking the user's role against the roles defined in the attribute.
This demonstrates how custom attributes can be leveraged in a practical and useful way in a real-world scenario.

Hope you find this blog post useful. Happy coding!

Read original blog post on my website https://antondevtips.com.

After reading the post consider the following:

  • Subscribe to receive newsletters with the latest blog posts
  • Download the source code for this post from my github (available for my sponsors on BuyMeACoffee and Patreon)

If you like my content —  consider supporting me

Unlock exclusive access to the source code from the blog posts by joining my Patreon and Buy Me A Coffee communities!

Top comments (0)