【问题标题】:Getting All Controllers and Actions names in C#在 C# 中获取所有控制器和操作名称
【发布时间】:2014-02-05 16:46:13
【问题描述】:

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

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

【问题讨论】:

标签: c# asp.net-mvc asp.net-mvc-controller


【解决方案1】:

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

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

你会得到以下输出:

【讨论】:

  • 有了这个答案,我可以立即得到结果,而接受的那个很难使用!
  • @LucianBumb - “MyWebDll” 只是您的 Web 应用程序(或 MvcApplication 类所在的任何命名空间)的主 dll 的占位符。查看 Global.asax.cs 文件,您会看到类似于“public class MvcApplication : System.Web.HttpApplication”的内容。将示例中的“MyWebDll”部分替换为您的 Web 应用程序 dll 或命名空间的名称(如果您不确定程序集名称,请检查您的项目属性窗口或 bin 文件夹)。例如,如果我的项目生成一个名为“AmosCo.Enterprise.Website.dll”的 dll,那么我将使用“AmosCo.Enterprise.Website.MvcApplication”
  • @David Létourneau 查看我的帖子
  • 如果您在 Web 项目中,可以将 MyWebDll 行替换为 var asm = Assembly.GetCallingAssembly()。
  • 要让这个查询在 LinqPad 中工作,按 F4 打开查询属性 > 浏览 > 转到应用程序的 bin/debug/MyApp.dll(或发布文件夹)并选择。对同一文件夹中的System.Web.Mvc.dll 也执行相同的操作。然后将MyWebDll 更改为MyApp(您的应用程序dll 的名称)。不要忘记添加转储语句。
【解决方案2】:

你可以使用反射找到当前程序集中的所有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)));

【讨论】:

  • @ArsenMkrt 好点,我认为必须用Action 属性标记方法。事实证明,所有公共方法都是动作,除非用NonAction 属性修饰。我已经更新了我的答案。
  • 我们可以更进一步,所有返回类型为 ActionResult 或继承自它的公共方法
  • @ArsenMkrt Action 方法不需要返回 ActionResult 的派生。例如,MVC 非常乐意执行在其签名中返回 string 的操作方法。
  • 返回类型为 void 的公共方法呢?没关系,我发现这篇 msdn 文章显示操作方法可以是 void msdn.microsoft.com/en-us/library/…
  • @Tareck117 如果您在每次请求时都调用它,可能。但这似乎不是您需要经常调用的东西。在大多数情况下,在启动时调用一次就足够了——性能命中可以忽略不计。如果您在运行时动态加载新程序集,则单独扫描一次。
【解决方案3】:

我一直在寻找一种获取区域、控制器和动作的方法,为此我设法对您在此处发布的方法进行了一些更改,所以如果有人正在寻找一种方法来获取 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()

【讨论】:

  • 我将您的代码与此字符串扩展名结合起来:stackoverflow.com/a/17253735/453142 以获取该区域。所以你可以得到这样的区域: Area = n.DeclaringType.Namespace.ToString().Substring("Areas.", ".Controllers") 您需要更新扩展名以返回 string.empty 而不是异常就是这样。它不那么丑了=)
  • var ind = method.DeclaringType?.Namespace?.IndexOf(".Areas.", StringComparison.InvariantCulture) ?? -1;面积 = ind > -1 ? method.DeclaringType?.Namespace?.Substring(ind + ".Areas.".Length).Replace(".Controllers", "") : null;
【解决方案4】:
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】:
    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());
               }
          }
    }
    

    【讨论】:

      【解决方案6】:

      如果它可以帮助任何人,我改进了 @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&lt;CustomType&gt; 时,最后一个属性 (PropertyDescription) 包含许多与 CustomType 的属性相关的元数据。因此,您可以获得每个CustomType's 属性的名称、类型、描述(添加[Description] 注释)等。

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


      更新

      更进一步,如果您想在无法修改的字段上添加一些 [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));
      

      (归功于this answer

      并将PropertyDescription 更新为public List&lt;string&gt; PropertyDescription { get; set; }

      【讨论】:

        【解决方案7】:

        使用Reflection,枚举从System.Web.MVC.Controller继承的程序集和过滤类中的所有类型,而不是将此类型的公共方法列为操作

        【讨论】:

          【解决方案8】:

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

          【讨论】:

          • 如何获得具有异步任务的方法
          • 您可以使用method.ReturnType == typeof(Task&lt;IActionResult&gt;) 并删除其他返回类型标准。
          【解决方案9】:

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

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

          获取已注册控制器(无论它们在哪里)的正确方法是要求此服务IActionDescriptorCollectionProvider

          ActionDescriptors 属性包含所有可用控制器的列表,包括路由、参数等。

          有关详细信息,请参阅MSDN 文档。

          已编辑您可以在this SO question 上找到更多信息。

          【讨论】:

          • 对于那些使用 NSwag 的人,利用您已经提供的管道:Program.CreateHostBuilder(new string[0]).Build().Services.GetRequiredService&lt;IActionDescriptorCollectionProvider&gt;()
          【解决方案10】:

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

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

          【讨论】:

            【解决方案11】:

            更新:

            对于.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);
                    
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-08-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多