C# – Get all classes with a custom attribute

To get all classes with a custom attribute, first get all types in the assembly, then use IsDefined(customAttributeType) to filter the types:

using System.Reflection;

var types = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsDefined(typeof(ApiControllerAttribute)));
Code language: C# (cs)

This is looking for classes in the current assembly that have the [ApiController] attribute, such as this controller class:

[ApiController]
[Route("[controller]")]
public class RandomNumberController : ControllerBase
{}
Code language: C# (cs)

This is useful in several scenarios, such as when you want to log information during startup or when you are auto-wiring classes.

In this article, I’ll show how to search all assemblies, how to handle inherited custom attributes, and how to get the custom attribute values (instead of just checking if they exist).

Search in all loaded assemblies

To search all assemblies, use AppDomain.CurrentDomain.GetAssemblies() to get an array of currently loaded assemblies, then loop through the assemblies and use GetType() + IsDefined() to get all classes that have a specific custom attribute.

The following Linq query simplifies this:

var types = from assembly in AppDomain.CurrentDomain.GetAssemblies()
			from type in assembly.GetTypes()
			where type.IsDefined(typeof(ApiControllerAttribute))
			select type;
Code language: C# (cs)

This is one case where the Linq query syntax is actually easier to understand than the method syntax:

AppDomain.CurrentDomain.GetAssemblies()
	.SelectMany(a => a.GetTypes().Where(t => t.IsDefined(typeof(ApiControllerAttribute))));
Code language: C# (cs)

If you want to look in assemblies that aren’t loaded, you can use the MetadataReader approach.

Inherited custom attributes

Custom attributes can be inherited. Consider the following example:

[ApiController]
[Route("[controller]")]
public abstract class ApiControllerBase : ControllerBase
{ }

public class RandomNumberController : ApiControllerBase
{}
Code language: C# (cs)

The [ApiController] attribute is defined on the ApiControllerBase abstract class, and it’s inherited by the RandomNumberController class. Searching for classes with the [ApiController] attribute would return both of these classes by default, which may be undesirable. I’ll show how to filter the query below.

Filter out abstract classes

It’s not very likely you’d actually want abstract classes returned in the search. You can filter them out by checking the Type.IsAbstract property, like this:

assembly.GetTypes().Where(t => t.IsDefined(typeof(ApiControllerAttribute)) && !t.IsAbstract)
Code language: C# (cs)

This would only return the RandomNumberController class, and not the ApiControllerBase abstract class.

Filter out classes that inherited the custom attribute

By default, IsDefined(customAttributeType) will return true even if the custom attribute was inherited. This is good default behavior, because that’s usually what you’d want.

However, there may be scenarios where you want to filter out classes that only have the custom attribute because they inherited it. To do that, you can pass in false for the inherit parameter:

assembly.GetTypes().Where(t => t.IsDefined(typeof(ApiControllerAttribute), inherit: false))
Code language: C# (cs)

Because the RandomNumberController class is inheriting the [ApiController] attribute from the ApiControllerBase class, and isn’t defining it directly, it will be filtered out.

Getting the custom attribute values

When a custom attribute has values, you’ll most likely want to look at the values. For example, the [Route] attribute is declared with a route template string.

[ApiController]
[Route("[controller]")]
public class RandomNumberController : ControllerBase
{}
Code language: C# (cs)

To get the custom attribute values, You can use GetCustomAttribute<customAttributeType>() to get the custom attribute object:

var assembly = Assembly.GetExecutingAssembly();

foreach (var type in assembly.GetTypes())
{
	var routeAttribute = type.GetCustomAttribute<RouteAttribute>();

	if (routeAttribute != null)
	{
		Console.WriteLine($"Controller={type.Name} RouteTemplate={routeAttribute.Template}");
	}
}
Code language: C# (cs)

If the custom attribute isn’t defined on the type, it returns null. Otherwise it returns the custom attribute object, and you can look at its properties.

This outputs the following:

Controller=RandomNumberController RouteTemplate=[controller]Code language: plaintext (plaintext)