Last active
August 29, 2015 14:19
-
-
Save romeshniriella/66b1411d716e6b19dd80 to your computer and use it in GitHub Desktop.
Extract all MVC / WebApi area/controller/actions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class Extensions{ | |
public static TInner IfNotNull<T, TInner>(this T source, Func<T, TInner> selector, bool createNew = false) | |
where T : class | |
{ | |
return source != null | |
? selector(source) : (createNew ? Activator.CreateInstance<TInner>() : default(TInner)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// http://stackoverflow.com/questions/5508050/how-to-get-a-property-value-based-on-the-name | |
// http://forums.asp.net/t/1600843.aspx?How+list+all+of+the+Actions+in+an+MVC+application+for+security+audit+ | |
// http://stackoverflow.com/questions/5801630/mvc-get-all-action-methods | |
// http://stackoverflow.com/questions/21583278/getting-all-controllers-and-actions-names-in-c-sharp | |
// http://stackoverflow.com/questions/15690834/asp-net-mvc4-list-of-all-areas | |
// http://stackoverflow.com/questions/1091853/error-message-unable-to-load-one-or-more-of-the-requested-types-retrieve-the-l | |
/// <summary> | |
/// Extract all MVC/WebApi area/controller/actions | |
/// </summary> | |
public class ResourceHelper | |
{ | |
private const string NamespacePrefix = "github"; | |
public static List<ApplicationResource> GetResources() | |
{ | |
var resources = new List<ApplicationResource>(); | |
try | |
{ | |
resources.AddRange(MapResources<IController>()); // MVC | |
resources.AddRange(MapResources<IHttpController>(true)); // Web API | |
} | |
catch (ReflectionTypeLoadException ex) | |
{ | |
var sb = new StringBuilder(); | |
foreach (Exception exSub in ex.LoaderExceptions) | |
{ | |
sb.AppendLine(exSub.Message); | |
var exFileNotFound = exSub as FileNotFoundException; | |
if (exFileNotFound != null) | |
{ | |
if (!string.IsNullOrEmpty(exFileNotFound.FusionLog)) | |
{ | |
sb.AppendLine("Fusion Log:"); | |
sb.AppendLine(exFileNotFound.FusionLog); | |
} | |
} | |
sb.AppendLine(); | |
} | |
string errorMessage = sb.ToString(); | |
//Display or log the error based on your application. | |
TraceHelper.Error(errorMessage); | |
} | |
return resources; | |
} | |
private static IEnumerable<ApplicationResource> MapResources<T>(bool api = false) | |
{ | |
var cSuffix = api ? "ApiController" : "Controller"; | |
var assemblies = AppDomain.CurrentDomain.GetAssemblies(); // currently loaded assemblies | |
var areaRegistrations = GetAreaRegistrations(assemblies).ToList(); | |
var controllerTypes = GetControllerTypes<T>(assemblies, cSuffix); | |
var controllerMethods = GetControllerActions(controllerTypes, api); | |
foreach (var controllerType in controllerMethods) | |
{ | |
string area = GetAreaName(controllerType, areaRegistrations); | |
string controller = controllerType.Key.Name.Replace(cSuffix, ""); | |
foreach (MethodInfo action in controllerType.Value) | |
{ | |
string actionName = action.Name; | |
bool attrGet = action.GetCustomAttributes(false).Any(a => a.GetType().IsAssignableFrom(typeof(HttpPostAttribute))); | |
yield return new ApplicationResource(area, controller, actionName, attrGet, api); | |
} | |
} | |
} | |
private static string GetAreaName( | |
KeyValuePair<Type, IEnumerable<MethodInfo>> controllerType, | |
IEnumerable<Type> areaRegistrations) | |
{ | |
if (controllerType.Key.Namespace != null && controllerType.Key.Namespace.Contains("Areas")) | |
{ | |
var areaNamespace = controllerType.Key.Namespace; | |
var regArea = areaRegistrations.First(a => a.Namespace != null && areaNamespace.Contains(a.Namespace)); | |
var areaNameProp = regArea.GetProperty("AreaName"); | |
var instance = Activator.CreateInstance(regArea); // have to create an instance before retrieving the property value | |
var areaNameValue = areaNameProp.GetValue(instance); | |
return areaNameValue.ToString(); | |
} | |
return string.Empty; | |
} | |
private static Dictionary<Type, IEnumerable<MethodInfo>> GetControllerActions(IEnumerable<Type> controllerTypes, bool api) | |
{ | |
var controllerMethods = controllerTypes.ToDictionary( | |
controllerType => controllerType, | |
controllerType => controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance) | |
.Where(m => | |
!m.IsDefined(typeof(NonActionAttribute)) | |
// NOTE : We are using 'HttpResponseMessage' as the return in every API action method. | |
// if that isn't the case with your API, | |
// something like this will get you to a close result : | |
// ==> (api || typeof(ActionResult).IsAssignableFrom(m.ReturnType)) | |
&& (api ? typeof(HttpResponseMessage).IsAssignableFrom(m.ReturnType) : typeof(ActionResult).IsAssignableFrom(m.ReturnType)) | |
&& m.Name != "Dispose" | |
&& !m.IsSpecialName | |
&& !m.IsStatic)); | |
return controllerMethods; | |
} | |
private static IEnumerable<Type> GetControllerTypes<T>(IEnumerable<Assembly> assemblies, string controllerSuffix = "Controller") | |
{ | |
var controllerTypes = assemblies | |
.SelectMany(a => a.GetTypes()) | |
.Where(t => t != null | |
&& t.IsPublic // public controllers only | |
&& t.Name.EndsWith(controllerSuffix, StringComparison.OrdinalIgnoreCase) // enfore naming convention | |
&& t.Namespace.IfNotNull(n => n.ToLowerInvariant().StartsWith(NamespacePrefix)) | |
&& !t.IsAbstract // no abstract controllers | |
&& typeof(T).IsAssignableFrom(t)); | |
// should implement T (happens automatically when you extend Controller/ApiController) | |
return controllerTypes; | |
} | |
private static IEnumerable<Type> GetAreaRegistrations(IEnumerable<Assembly> assemblies) | |
{ | |
var areaRegistrations = assemblies.SelectMany(a => a.GetTypes()) | |
.Where(t => t != null && typeof(AreaRegistration).IsAssignableFrom(t)); | |
return areaRegistrations; | |
} | |
} | |
public class ApplicationResource | |
{ | |
public ApplicationResource(string area, string controller, string action, bool post, bool api) | |
{ | |
Area = area; | |
Controller = controller; | |
Action = action; | |
IsGet = !post; | |
IsApi = api; | |
} | |
public string Area { get; set; } | |
public string Controller { get; set; } | |
public string Action { get; set; } | |
public bool IsGet { get; set; } | |
public bool IsApi { get; set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment