Getting All Controllers and Actions names in C#

前端 未结 9 1685
耶瑟儿~
耶瑟儿~ 2020-11-27 10:56

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.

9条回答
  •  伪装坚强ぢ
    2020-11-27 11:32

    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().FirstOrDefault()?.Name ?? x.Name,
                Description = x.GetCustomAttributes().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() )
                                            .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> PropertyDescription { get; set; }
    }
    

    As my endpoints return IQueryable, 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().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().FirstOrDefault()?.Name ?? x.Name,
                    Description = x.GetCustomAttributes().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 PropertyDescription { get; set; }

提交回复
热议问题