Get all class that implement class with specific generic calsses - c#

I would like to find all classes in my program that implements a given class with a specific call as the generic types.
In the example below would i like to find all classes that implements MyBaseClass<MyScraper, MyElance> in this case would it be MyProperty and not OtherProperty as it implements other generic classes.
How can this be done?
public class MyProperty : MyBaseClass<MyScraper, MyElance>
{
public override string test()
{
var test = base.test();
test += " + the new";
return test;
}
}
public class OtherProperty : MyBaseClass<OtherScraper, OtherElance>
{
public override string test()
{
var test = base.test();
test += " + the other";
return test;
}
}
public class MyBaseClass<S, E>
where S : IScraper
where E : IElance
{
public virtual string test()
{
return "base";
}
}
Edit:
Found a solution, but please tell me if there are a better way
var test = from x in Assembly.GetAssembly(typeof(Program)).GetTypes()
let y = x.BaseType
where !x.IsAbstract && !x.IsInterface &&
y != null && y.IsGenericType &&
y.GetGenericTypeDefinition() == typeof(MyBaseClass<,>) &&
y.GenericTypeArguments[0] == typeof(MyScraper) &&
y.GenericTypeArguments[1] == typeof(MyElance)
select x;
Final solution:
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(x => !x.IsAbstract && !x.IsInterface)
.Where(x => x.BaseType != null &&
x.BaseType.IsGenericType &&
x.BaseType.GetGenericTypeDefinition() == typeof (MyBaseClass<,>) &&
x.BaseType.GenericTypeArguments[0] == typeof (MyScraper) &&
x.BaseType.GenericTypeArguments[1] == typeof (MyElance))
.Select(x => x).ToList();

Assembly scanning is pretty much the best way.
In addition to the algorythm you have, I recommend scanning the AppDomain instead of the specific assembly - this will allow you (or others!) toextend your library more readily...
AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
//...
(Disclaimer Pardon the use of Linq calls here, but I detest syntactical linq)
In addition, make sure any other assemblies are loaded, to ensure you don't miss something at scan-time.

var searchType = typeof(MyBaseClass<MyScraper, MyElance>);
var types =
AppDomain.CurrentDomain.GetAssemblies()
// For search only on assemblies where could exsist this types
.Where(a => a.GetName().Name == searchType.Assembly.GetName().Name || a.GetReferencedAssemblies().Any(n => n.Name == searchType.Assembly.GetName().Name))
.Where(t => !t.IsAbstract && !t.IsInterface)
.Select(t => t.GetTypes().Where(a => searchType.IsAssignableFrom(a)))
.SelectMany(a => a);
I use this version, to search in all loaded assemblies, with additional filter not to process assemblies that could not contain my type

Try this:
//var desiredImplementation = typeof (MyBaseClass<>).MakeGenericType(typeof (MyScraper), typeof (MyElance));
var desiredImplementation = typeof (MyBaseClass<MyScraper, MyElance>);
var implementingTypes = Assembly
.GetExecutingAssembly()
.GetExportedTypes()
.Where(type => desiredImplementation.IsAssignableFrom(type))
.ToList();
You can replace GetExportedTypes() with GetTypes() and BindingFlags to further introspect the assembly too.

Related

Get RuntimeMethodInfo from generic overloaded methods in non-generic static class

I try to get runtime method infoes in static class. I have four static method inside the class and each name is equal, also parameter name is equal. The only difference is their types. One of four method has string parameter so it is easy to get method info. However the others don't working. I find several advice but that are not working.
All test code is here.
class Program {
static void Main(string[] args) {
//ok
var stringMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(string) });
//not working
var dictMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(Dictionary<,>) });
//not working
var genericMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(object) });
//not working
var listMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(List<>) });
//not working
var res = typeof(TestClass)
.GetRuntimeMethods()
.Where(x => x.Name.Equals("TestMethod"))
.Select(m => new { Method = m, Parameters = m.GetParameters() })
.FirstOrDefault(p =>
p.Parameters.Length == 1
&& p.Parameters[0].ParameterType.IsGenericType
&& p.Parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(ICollection<>)
);
}
}
public static class TestClass {
public static bool TestMethod(string item) {
return true;
}
public static bool TestMethod<TKey, TValue>(Dictionary<TKey, TValue> item) {
return true;
}
public static bool TestMethod<T>(T item) {
return true;
}
public static bool TestMethod<T>(List<T> item) {
return true;
}
}
If you are using .net core 2.1 or greater, you can use Type.MakeGenericMethodParameter to let you refer to a generic parameter of a method. You can use that to create a generic type argument that will work with GetMethod (not available for GetRuntimeMethod).
var stringMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(string) });
Type[] dictionaryTypeParameters = { typeof(Dictionary<,>).MakeGenericType(Type.MakeGenericMethodParameter(0), Type.MakeGenericMethodParameter(1)) };
MethodInfo dictMethodInfo = typeof(TestClass).GetMethod("TestMethod", 2, dictionaryTypeParameters);
MethodInfo listMethodInfo = typeof(TestClass).GetMethod("TestMethod", 1, new[] { typeof(List<>).MakeGenericType(Type.MakeGenericMethodParameter(0)) });
MethodInfo genericMethodInfo = typeof(TestClass).GetMethod("TestMethod", 1, new[] { Type.MakeGenericMethodParameter(0) });
Some interesting reading on the topic here.
Let's say we would like to use this method to get any MethodInfo for the various TestMethod. Note that they all have exactly one parameter, so p.Parameters.Length == 1 is useless:
Defined as bool TestMethod(string item). We can use
.FirstOrDefault(p => p.Method.IsGenericMethod)
Defined as bool TestMethod<TKey, TValue>(Dictionary<TKey, TValue> item)
.FirstOrDefault(p =>
p.Method.IsGenericMethod &&
p.Method.GetGenericArguments().Length == 2)
Defined as bool TestMethod<T>(T item)
.FirstOrDefault(p =>
p.Method.IsGenericMethod &&
p.Parameters[0].ParameterType == m.Method.GetGenericArguments()[0]
)
Defined as TestMethod<T>(List<T> item)
.FirstOrDefault(p =>
p.Method.IsGenericMethod &&
p.Parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(List<>)
)
In case of generic methods you have to query the MethodInfo object to get the appropriate method.
You can do it as below -
var dictMethodInfo = typeof(TestClass).GetMethods().Single(m => m.Name == "TestMethod" && m.IsGenericMethod &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.IsGenericType &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Dictionary<,>));
In your case, getting the MethodInfo for TestMethod<T> is bit tricky, but below should work -
var genericMethodInfo = typeof(TestClass).GetMethods().Single(m => m.Name == "TestMethod" && m.IsGenericMethod &&
!m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.IsGenericType);
Final Code -
var stringMethodInfo = typeof(TestClass).GetRuntimeMethod("TestMethod", new[] { typeof(string) });
var dictMethodInfo = typeof(TestClass).GetMethods().Single(m => m.Name == "TestMethod" && m.IsGenericMethod &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.IsGenericType &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(Dictionary<,>));
var genericMethodInfo = typeof(TestClass).GetMethods().Single(m => m.Name == "TestMethod" && m.IsGenericMethod &&
!m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.IsGenericType);
var listMethodInfo = typeof(TestClass).GetMethods().Single(m => m.Name == "TestMethod" && m.IsGenericMethod &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.IsGenericType &&
m.GetGenericMethodDefinition().GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(List<>));

Query for static type in Linq

When I query for abstract types using Linq, it also grabs static classes.
IEnumerable<Type> FilterInheritable()
{
var q = Assembly.Load("Assembly-CSharp").GetTypes()
.Where(x => x.IsAbstract == true);
return q;
}
Is it possible to filter out the static types? Something like this?
IEnumerable<Type> FilterInheritable()
{
var q = Assembly.Load("Assembly-CSharp").GetTypes()
.Where(x => x.IsAbstract == true)
.Where(x => x.IsStatic != true);
return q;
}
Since static classes are also sealed by definition, but abstract classes cannot be sealed, you can do this:
var q = Assembly.Load("Assembly-CSharp").GetTypes()
.Where(x => x.IsAbstract && x.IsClass && !x.IsSealed);
I added IsClass to exclude interfaces as well.

Obtaining a list of subtypes of generic class

I have a generic class with many subtypes:
public abstract class MyClass<T> : MyBaseClass where T : class
{...}
public class MySubClassA : MyClass<A>
{...}
public class MySubClassB : MyClass<B>
{...}
Is there an easy way to search for subclasses of MyClass and obtain an IEnumerable<Type> containing MySubClassA and MySubClassB?
I have used this method before, but I am not sure how to adapt it for use with generics:
public static IEnumerable<Type> GetSubTypesOf(Type t, bool baseAssemblyOnly = false)
{
List<Type> types = new List<Type>();
Assembly[] searchAssemblies = baseAssemblyOnly
? new[] { Assembly.GetAssembly(t) }
: AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in searchAssemblies)
{
types.AddRange(a.GetTypes()
.Where(myType => myType.IsClass
&& !myType.IsAbstract
&& myType.IsSubclassOf(t)));
}
return types;
}
This is a bit complicated since you have to search the base types of a type to find one which matches the open generic type definition of MyClass<>. You can define a couple of helper methods:
public static IEnumerable<Type> BaseTypesOf(Type t)
{
while (t != null)
{
yield return t;
t = t.BaseType;
}
}
public static Type FindGenericBaseTypeOf(Type t, Type openType)
{
return BaseTypesOf(t)
.FirstOrDefault(bt => bt.IsGenericType && bt.GetGenericTypeDefinition() == openType);
}
then you can apply them to the incoming sequence of types to search e.g.
var types = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => t.IsClass && !t.IsAbstract)
.Select(t => new { Type = t, GenericBase = FindGenericBaseTypeOf(t, typeof(MyClass<>)) })
.Where(ts => ts.GenericBase != null)
.Select(ts => ts.GenericBase.GetGenericArguments().First())
.ToArray();
The problem is that when you pass typeof(MyClass<>) for the t parameter, you are not passing an instantiated generic type, but a generic type definition. This means that none of your classes would respond to IsSubclassOf(t).
You can fix your code as follows:
List<Type> types = searchAssemblies
.SelectMany(a =>
a.GetTypes()
.Where(myType => myType.IsClass && !myType.IsAbstract && HasGenericBase(myType, t))
).ToList();
...
private static bool HasGenericBase(Type myType, Type t) {
Debug.Assert(t.IsGenericTypeDefinition);
while (myType != typeof(object)) {
if (myType.IsGenericType && myType.GetGenericTypeDefinition() == t) {
return true;
}
myType = myType.BaseType;
}
return false;
}

TinyIoC Resolve Plugin Contract

How would one go about resolving Plugin contacts using TinyIoC?
Host.exe /w reference to Core.Contract.dll
var container = new TinyIoCContainer();
container.AutoRegister(new[] { Assembly.LoadFrom("Core.Contracts.dll") },
DuplicateImplementationActions.RegisterMultiple);
container.AutoRegister(new[] { Assembly.LoadFrom("EchoCodeAnalysis.dll") },
DuplicateImplementationActions.RegisterMultiple);
var mi = container.Resolve<IService>();
the contract IService in in Core.Contracts.dll and is reference in the host assembly, this is to give the drag and drop plugin a chance to work without recompilation.
In EchoCodeAnalysis.dll we have the actual plugin implementation which is not referenced in the host assembly but share the host of Core.Contracts.dll using the IService.
Core.Contract.dll:
public interface IService
{
string ID { get; set; }
}
EchoCodeAnalysis.dll:
public class Service : IService
{
string IService.ID
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
}
EDIT:
I managed to resolve the first part of my issue.
var type = typeof(IService);
var types = (new[] { Assembly.LoadFrom("EchoCodeAnalysis.dll") }).ToList()
.SelectMany(s => s.GetTypes())
.Where(x => type.IsAssignableFrom(x) && x.IsClass).ToList();
container.RegisterMultiple<IService>(types.ToArray());
var mi = container.ResolveAll<Core.Contracts.IService>();
Will fetch and resolve all IService interfaces, that limits the plugin to that interface and not any high up implementations. Say, IMenuItem impltemented as IService, the code above could find any class that is traced back to the origin of IService, but those that explicitly implement IMenuItem which has lets say name, when resolved as IService it will only fetch the IService properties and not include IMenuItem properties.
That is where. container.Register(types.ToArray()).AsRespectiveImplementations() would come in handy.
But is there anywhere around this issue? or is this a utility that has to be written up to extend TinyIOC?
Edit 2:
We have then moved to a extension but we are still not getting anything resolved.
public static IEnumerable<T> GetPluginsForModule<T>()
{
var type = typeof(T);
var types = Plugins.SelectMany(s => s.GetTypes())
.Where(x => type.IsAssignableFrom(x) && x.IsClass).ToList();
foreach (var t in types)
{
if (t.CustomAttributes.Where(x => x.AttributeType == typeof(CluraPlugin)).Any())
{
CustomAttributeData attr = t.CustomAttributes.Where(x => x.AttributeType == typeof(CluraPlugin)).FirstOrDefault();
if (attr == null)
break;
string Name = attr.ConstructorArguments.Where(x => x.ArgumentType == typeof(string)).FirstOrDefault().Value as string;
Type InterfaceTypeArgument = attr.ConstructorArguments.Where(x => x.ArgumentType == typeof(Type)).FirstOrDefault().Value as Type;
Container.Register(InterfaceTypeArgument, t, Name).AsMultiInstance();
}
}
return Container.ResolveAll(type) as IEnumerable<T>;
}
I'm passing the correct values, in the Container.Register above we have
InterfaceTypeArgument = IMenuItem, t = EchoMenu : IMenuItem, Name = "EchoMenu"
but when we ask the container to resolve IMenuItem after registering EchoMenu as its implementation we get null back from resolve all.
Any thoughts?
I found a way, not sure about best practice or potential memory issues down the road, with benchmark testing.
Using:
string PluginDirectory = Directory.GetCurrentDirectory() +"\\Plugins\\";
PluginManager.LoadDirectory(PluginDirectory);
var mi = PluginManager.GetPluginsForModule<IService>();
Which resolves things like this:
public static IEnumerable<object> GetPluginsForModule<T>()
{
var type = typeof(T);
var types = Plugins.SelectMany(s => s.GetTypes())
.Where(x => type.IsAssignableFrom(x) && x.IsClass).ToList();
foreach (var t in types)
{
if (t.CustomAttributes.Where(x => x.AttributeType == typeof(CluraPlugin)).Any())
{
CustomAttributeData attr = t.CustomAttributes.Where(x => x.AttributeType == typeof(CluraPlugin)).FirstOrDefault();
if (attr == null)
break;
string Name = attr.ConstructorArguments.Where(x => x.ArgumentType == typeof(string)).FirstOrDefault().Value as string;
Type InterfaceTypeArgument = attr.ConstructorArguments.Where(x => x.ArgumentType == typeof(Type)).FirstOrDefault().Value as Type;
Container.Register(InterfaceTypeArgument, t, Name).AsMultiInstance();
}
}
return Container.ResolveAll(type);
}
It might not be optimal when doing things on the fly, so there might need to be a need for a plugin manager to hold implementations as instances once you start the application and use them from list of plugin types.

Getting All Controllers and Actions names in C#

Is it possible to list the names of all controllers and their actions programmatically?
I want to implement database driven security for each controller and action. As a developer, I know all controllers and actions and can add them to a database table, but is there any way to add them automatically?
The following will extract controllers, actions, attributes and return types:
Assembly asm = Assembly.GetAssembly(typeof(MyWebDll.MvcApplication));
var controlleractionlist = asm.GetTypes()
.Where(type=> typeof(System.Web.Mvc.Controller).IsAssignableFrom(type))
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
.Where(m => !m.GetCustomAttributes(typeof( System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any())
.Select(x => new {Controller = x.DeclaringType.Name, Action = x.Name, ReturnType = x.ReturnType.Name, Attributes = String.Join(",", x.GetCustomAttributes().Select(a => a.GetType().Name.Replace("Attribute",""))) })
.OrderBy(x=>x.Controller).ThenBy(x => x.Action).ToList();
If you run this code in linqpad for instance and call
controlleractionlist.Dump();
you get the following output:
You can use reflection to find all Controllers in the current assembly, and then find their public methods that are not decorated with the NonAction attribute.
Assembly asm = Assembly.GetExecutingAssembly();
asm.GetTypes()
.Where(type=> typeof(Controller).IsAssignableFrom(type)) //filter controllers
.SelectMany(type => type.GetMethods())
.Where(method => method.IsPublic && ! method.IsDefined(typeof(NonActionAttribute)));
I was looking for a way to get Area, Controller and Action and for this I manage to change a little the methods you post here, so if anyone is looking for a way to get the AREA here is my ugly method (which I save to an xml):
public static void GetMenuXml()
{
var projectName = Assembly.GetExecutingAssembly().FullName.Split(',')[0];
Assembly asm = Assembly.GetAssembly(typeof(MvcApplication));
var model = asm.GetTypes().
SelectMany(t => t.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
.Where(d => d.ReturnType.Name == "ActionResult").Select(n => new MyMenuModel()
{
Controller = n.DeclaringType?.Name.Replace("Controller", ""),
Action = n.Name,
ReturnType = n.ReturnType.Name,
Attributes = string.Join(",", n.GetCustomAttributes().Select(a => a.GetType().Name.Replace("Attribute", ""))),
Area = n.DeclaringType.Namespace.ToString().Replace(projectName + ".", "").Replace("Areas.", "").Replace(".Controllers", "").Replace("Controllers", "")
});
SaveData(model.ToList());
}
Edit:
//assuming that the namespace is ProjectName.Areas.Admin.Controllers
Area=n.DeclaringType.Namespace.Split('.').Reverse().Skip(1).First()
All these answers rely upon reflection, and although they work, they try to mimic what the middleware does.
Additionally, you may add controllers in different ways, and it is not rare to have the controllers shipped in multiple assemblies. In such cases, relying on reflection requires too much knowledge: for example, you have to know which assemblies are to be included, and when controllers are registered manually, you might choose a specific controller implementation, thus leaving out some legit controllers that would be picked up via reflection.
The proper way in ASP.NET Core to get the registered controllers (wherever they are) is to require this service IActionDescriptorCollectionProvider.
The ActionDescriptors property contains the list of all the actions available. Each ControllerActionDescriptor provides details
including names, types, routes, arguments and so on.
var adcp = app.Services.GetRequiredService<IActionDescriptorCollectionProvider>();
var descriptors = adcp.ActionDescriptors
.Items
.OfType<ControllerActionDescriptor>();
For further information, please see the MSDN documentation.
Edited You may find more information on this SO question.
var result = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(type => typeof(ApiController).IsAssignableFrom(type))
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
.Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any())
.GroupBy(x => x.DeclaringType.Name)
.Select(x => new { Controller = x.Key, Actions = x.Select(s => s.Name).ToList() })
.ToList();
Assembly assembly = Assembly.LoadFrom(sAssemblyFileName)
IEnumerable<Type> types = assembly.GetTypes().Where(type => typeof(Controller).IsAssignableFrom(type)).OrderBy(x => x.Name);
foreach (Type cls in types)
{
list.Add(cls.Name.Replace("Controller", ""));
IEnumerable<MemberInfo> memberInfo = cls.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public).Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any()).OrderBy(x => x.Name);
foreach (MemberInfo method in memberInfo)
{
if (method.ReflectedType.IsPublic && !method.IsDefined(typeof(NonActionAttribute)))
{
list.Add("\t" + method.Name.ToString());
}
}
}
If it may helps anyone, I improved #AVH's answer to get more informations using recursivity.
My goal was to create an autogenerated API help page :
Assembly.GetAssembly(typeof(MyBaseApiController)).GetTypes()
.Where(type => type.IsSubclassOf(typeof(MyBaseApiController)))
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
.Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any())
.Select(x => new ApiHelpEndpointViewModel
{
Endpoint = x.DeclaringType.Name.Replace("Controller", String.Empty),
Controller = x.DeclaringType.Name,
Action = x.Name,
DisplayableName = x.GetCustomAttributes<DisplayAttribute>().FirstOrDefault()?.Name ?? x.Name,
Description = x.GetCustomAttributes<DescriptionAttribute>().FirstOrDefault()?.Description ?? String.Empty,
Properties = x.ReturnType.GenericTypeArguments.FirstOrDefault()?.GetProperties(),
PropertyDescription = x.ReturnType.GenericTypeArguments.FirstOrDefault()?.GetProperties()
.Select(q => q.CustomAttributes.SingleOrDefault(a => a.AttributeType.Name == "DescriptionAttribute")?.ConstructorArguments ?? new List<CustomAttributeTypedArgument>() )
.ToList()
})
.OrderBy(x => x.Controller)
.ThenBy(x => x.Action)
.ToList()
.ForEach(x => apiHelpViewModel.Endpoints.Add(x)); //See comment below
(Just change the last ForEach() clause as my model was encapsulated inside another model).
The corresponding ApiHelpViewModel is :
public class ApiHelpEndpointViewModel
{
public string Endpoint { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public string DisplayableName { get; set; }
public string Description { get; set; }
public string EndpointRoute => $"/api/{Endpoint}";
public PropertyInfo[] Properties { get; set; }
public List<IList<CustomAttributeTypedArgument>> PropertyDescription { get; set; }
}
As my endpoints return IQueryable<CustomType>, the last property (PropertyDescription) contains a lot of metadatas related to CustomType's properties. So you can get the name, type, description (added with a [Description] annotation) etc... of every CustomType's properties.
It goes further that the original question, but if it can help someone...
UPDATE
To go even further, if you want to add some [DataAnnotation] on fields you can't modify (because they've been generated by a Template for example), you can create a MetadataAttributes class :
[MetadataType(typeof(MetadataAttributesMyClass))]
public partial class MyClass
{
}
public class MetadataAttributesMyClass
{
[Description("My custom description")]
public int Id {get; set;}
//all your generated fields with [Description] or other data annotation
}
BE CAREFUL : MyClass MUST be :
A partial class,
In the same namespace as the generated MyClass
Then, update the code which retrieves the metadatas :
Assembly.GetAssembly(typeof(MyBaseController)).GetTypes()
.Where(type => type.IsSubclassOf(typeof(MyBaseController)))
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
.Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any())
.Select(x =>
{
var type = x.ReturnType.GenericTypeArguments.FirstOrDefault();
var metadataType = type.GetCustomAttributes(typeof(MetadataTypeAttribute), true)
.OfType<MetadataTypeAttribute>().FirstOrDefault();
var metaData = (metadataType != null)
? ModelMetadataProviders.Current.GetMetadataForType(null, metadataType.MetadataClassType)
: ModelMetadataProviders.Current.GetMetadataForType(null, type);
return new ApiHelpEndpoint
{
Endpoint = x.DeclaringType.Name.Replace("Controller", String.Empty),
Controller = x.DeclaringType.Name,
Action = x.Name,
DisplayableName = x.GetCustomAttributes<DisplayAttribute>().FirstOrDefault()?.Name ?? x.Name,
Description = x.GetCustomAttributes<DescriptionAttribute>().FirstOrDefault()?.Description ?? String.Empty,
Properties = x.ReturnType.GenericTypeArguments.FirstOrDefault()?.GetProperties(),
PropertyDescription = metaData.Properties.Select(e =>
{
var m = metaData.ModelType.GetProperty(e.PropertyName)
.GetCustomAttributes(typeof(DescriptionAttribute), true)
.FirstOrDefault();
return m != null ? ((DescriptionAttribute)m).Description : string.Empty;
}).ToList()
};
})
.OrderBy(x => x.Controller)
.ThenBy(x => x.Action)
.ToList()
.ForEach(x => api2HelpViewModel.Endpoints.Add(x));
(Credit to this answer)
and update PropertyDescription as public List<string> PropertyDescription { get; set; }
Use Reflection, enumerate all types inside the assembly and filter classes inherited from System.Web.MVC.Controller, than list public methods of this types as actions
Or, to whittle away at #dcastro 's idea and just get the controllers:
Assembly.GetExecutingAssembly()
.GetTypes()
.Where(type => typeof(Controller).IsAssignableFrom(type))
#decastro answer is good. I add this filter to return only public actions those have been declared by the developer.
var asm = Assembly.GetExecutingAssembly();
var methods = asm.GetTypes()
.Where(type => typeof(Controller)
.IsAssignableFrom(type))
.SelectMany(type => type.GetMethods())
.Where(method => method.IsPublic
&& !method.IsDefined(typeof(NonActionAttribute))
&& (
method.ReturnType==typeof(ActionResult) ||
method.ReturnType == typeof(Task<ActionResult>) ||
method.ReturnType == typeof(String) ||
//method.ReturnType == typeof(IHttpResult) ||
)
)
.Select(m=>m.Name);
Update:
For .NET 6 minimal hosting model see this answer on how to replace Startup in the code below
https://stackoverflow.com/a/71026903/3850405
Original:
In .NET Core 3 and .NET 5 you can do it like this:
Example:
public class Example
{
public void ApiAndMVCControllers()
{
var controllers = GetChildTypes<ControllerBase>();
foreach (var controller in controllers)
{
var actions = controller.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public);
}
}
private static IEnumerable<Type> GetChildTypes<T>()
{
var types = typeof(Startup).Assembly.GetTypes();
return types.Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
}

Categories

Resources