是否可以以编程方式列出所有控制器的名称及其操作?
我想为每个控制器和操作实现数据库驱动的安全性。作为一名开发人员,我知道所有控制器和操作,并且可以将它们添加到数据库表中,但是有没有办法自动添加它们?
是否可以以编程方式列出所有控制器的名称及其操作?
我想为每个控制器和操作实现数据库驱动的安全性。作为一名开发人员,我知道所有控制器和操作,并且可以将它们添加到数据库表中,但是有没有办法自动添加它们?
下面将提取控制器、动作、属性和返回类型:
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();
你得到以下输出:
可以使用反射找到当前程序集中的所有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)));
我一直在寻找一种获取区域、控制器和动作的方法,为此我设法更改了您在此处发布的方法,所以如果有人正在寻找一种方法来获取 区域,这里是我丑陋的方法(我保存到一个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()
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());
}
}
}
如果它可以帮助任何人,我改进了@AVH 的答案以使用递归获取更多信息。
我的目标是创建一个自动生成的 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; }
使用反射,枚举程序集内的所有类型并过滤继承自的类 System.Web.MVC.Controller
,而不是将此类型的公共方法列为操作
@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);
所有这些答案都依赖于反射,虽然它们有效,但它们试图模仿中间件的作用。
此外,您可以通过不同的方式添加控制器,并且控制器在多个组件中发货的情况并不少见。在这种情况下,依赖反射需要太多的知识:例如,您必须知道要包含哪些程序集,并且当手动注册控制器时,您可能会选择特定的控制器实现,从而省略了一些合法的控制器通过反射拾取。
获取注册控制器(无论它们在哪里)的正确方法是要求此服务IActionDescriptorCollectionProvider
。
该ActionDescriptors
属性包含所有可用控制器的列表,包括路由、参数等。
有关详细信息,请参阅MSDN文档。
已编辑您可能会找到有关此 SO question的更多信息。
或者,削减@dcastro 的想法并获得控制器:
Assembly.GetExecutingAssembly()
.GetTypes()
.Where(type => typeof(Controller).IsAssignableFrom(type))
更新:
对于最小的托管模型,请参阅下面的代码中.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);
}
}