在 C# 中获取所有控制器和操作名称

问题描述 投票:0回答:13

是否可以以编程方式列出所有控制器的名称及其操作?

我想为每个控制器和操作实现数据库驱动的安全性。作为一名开发人员,我知道所有控制器和操作,并且可以将它们添加到数据库表中,但是有什么方法可以自动添加它们吗?

c# asp.net-mvc asp.net-mvc-controller
13个回答
130
投票

以下内容将提取控制器、操作、属性和返回类型:

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();

如果您在 linqpad 中运行此代码并调用

controlleractionlist.Dump();

您将得到以下输出:

enter image description here


97
投票

可以使用反射来查找当前程序集中的所有Controller,然后找到它们的未使用

NonAction
属性修饰的公共方法。

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)));

13
投票

所有这些答案都依赖于反思,尽管它们有效,但它们试图模仿中间件的作用。

此外,您可以通过不同的方式添加控制器,并且控制器以多个组件形式运输的情况并不罕见。在这种情况下,依赖反射需要太多的知识:例如,您必须知道要包含哪些程序集,并且当手动注册控制器时,您可能会选择特定的控制器实现,从而遗漏了一些合法的控制器通过反射拾取。

ASP.NET Core 中获取注册控制器(无论它们在哪里)的正确方法是需要此服务

IActionDescriptorCollectionProvider

ActionDescriptors
属性包含所有可用操作的列表。每个
ControllerActionDescriptor
提供详细信息 包括名称、类型、路由、参数等。

var adcp = app.Services.GetRequiredService<IActionDescriptorCollectionProvider>();
var descriptors = adcp.ActionDescriptors
                      .Items
                      .OfType<ControllerActionDescriptor>();

有关更多信息,请参阅 MSDN 文档。

已编辑您可以找到有关这个SO问题的更多信息。


8
投票

我一直在寻找一种获取区域、控制器和操作的方法,为此我设法改变了您在此处发布的一些方法,所以如果有人正在寻找一种方法来获取AREA,这是我丑陋的方法(我将其保存到 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());
    }

编辑:

//assuming that the namespace is ProjectName.Areas.Admin.Controllers

 Area=n.DeclaringType.Namespace.Split('.').Reverse().Skip(1).First()

5
投票
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();

5
投票

如果它可以帮助任何人,我改进了@AVH的answer以使用递归性获取更多信息。
我的目标是创建一个自动生成的 API 帮助页面 :

 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

(只需更改最后一个

ForEach()
子句,因为我的模型封装在另一个模型中)。
对应的
ApiHelpViewModel
是:

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; }
}

当我的端点返回

IQueryable<CustomType>
时,最后一个属性 (
PropertyDescription
) 包含许多与
CustomType
的属性相关的元数据。因此,您可以获得每个
[Description]
属性的名称、类型、描述(添加了
CustomType's
注释)等。

它比原来的问题更进一步,但如果它可以帮助某人......


更新

更进一步,如果您想在无法修改的字段上添加一些

[DataAnnotation]
(因为它们是由模板生成的),您可以创建一个 MetadataAttributes 类:

[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
}

小心

MyClass
必须

  • 部分课程,
  • 与生成的名称空间相同
    MyClass

然后,更新检索元数据的代码:

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));

(归功于这个答案

并将

PropertyDescription
更新为
public List<string> PropertyDescription { get; set; }


3
投票
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());
           }
      }
}

3
投票

更新:

对于

.NET 6
最小托管模型,请参阅此答案,了解如何替换下面代码中的
Startup

https://stackoverflow.com/a/71026903/3850405

原文:

在.NET Core 3和.NET 5中你可以这样做:

示例:

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);
        
    }
}

2
投票

或者,削弱@dcastro 的想法并只获得控制器:

Assembly.GetExecutingAssembly()
.GetTypes()
.Where(type => typeof(Controller).IsAssignableFrom(type))

1
投票

使用Reflection,枚举从

System.Web.MVC.Controller
继承的程序集和过滤器类中的所有类型,然后将此类型的公共方法列为操作


1
投票

@decastro 答案很好。我添加此过滤器以仅返回开发人员已声明的公共操作。

        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);

0
投票

您还可以在解决方案中找到所有控制器和所有操作

(多个控制器在一起)

var controlleractionlist = asm.GetTypes()

.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();

var list = controlleractionlist.Where(a => a.Controller.Contains("Controller")).ToList();

0
投票

您可以注入 IActionDescriptorCollectionProvider 接口并获取控制器的所有操作。

 private readonly IActionDescriptorCollectionProvider _collectionProvider;

 var acctions = _collectionProvider
             .ActionDescriptors
             .Items
             .Select(action => action as ControllerActionDescriptor)
             //.Where() //If you need  
             .ToList();
© www.soinside.com 2019 - 2024. All rights reserved.