views:

369

answers:

2

Is it possible to get all controllers available to a ControllerFactory?
What I want to do is get a list of all controller types in application, but in a consistent way.

So that all controllers I get are the same ones default request resolution is using.

(The actual task is to find all action methods that have a given attribute).

+4  A: 

You can use reflection to enumerate all classes in an assembly, and filter only classes inherit from Controller class.

The best reference is asp.net mvc source code. Take a look of the implementations of ControllerTypeCache and ActionMethodSelector class. ControllerTypeCache shows how to get all controller classes available.

       internal static bool IsControllerType(Type t) {
            return
                t != null &&
                t.IsPublic &&
                t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
                !t.IsAbstract &&
                typeof(IController).IsAssignableFrom(t);
        }

 public void EnsureInitialized(IBuildManager buildManager) {
            if (_cache == null) {
                lock (_lockObj) {
                    if (_cache == null) {
                        List<Type> controllerTypes = GetAllControllerTypes(buildManager);
                        var groupedByName = controllerTypes.GroupBy(
                            t => t.Name.Substring(0, t.Name.Length - "Controller".Length),
                            StringComparer.OrdinalIgnoreCase);
                        _cache = groupedByName.ToDictionary(
                            g => g.Key,
                            g => g.ToLookup(t => t.Namespace ?? String.Empty, StringComparer.OrdinalIgnoreCase),
                            StringComparer.OrdinalIgnoreCase);
                    }
                }
            }
        }

And ActionMethodSelector shows how to check if a method has desired attribute.

private static List<MethodInfo> RunSelectionFilters(ControllerContext controllerContext, List<MethodInfo> methodInfos) {
            // remove all methods which are opting out of this request
            // to opt out, at least one attribute defined on the method must return false

            List<MethodInfo> matchesWithSelectionAttributes = new List<MethodInfo>();
            List<MethodInfo> matchesWithoutSelectionAttributes = new List<MethodInfo>();

            foreach (MethodInfo methodInfo in methodInfos) {
                ActionMethodSelectorAttribute[] attrs = (ActionMethodSelectorAttribute[])methodInfo.GetCustomAttributes(typeof(ActionMethodSelectorAttribute), true /* inherit */);
                if (attrs.Length == 0) {
                    matchesWithoutSelectionAttributes.Add(methodInfo);
                }
                else if (attrs.All(attr => attr.IsValidForRequest(controllerContext, methodInfo))) {
                    matchesWithSelectionAttributes.Add(methodInfo);
                }
            }

            // if a matching action method had a selection attribute, consider it more specific than a matching action method
            // without a selection attribute
            return (matchesWithSelectionAttributes.Count > 0) ? matchesWithSelectionAttributes : matchesWithoutSelectionAttributes;
        }
Raymond
Internal, I wish they didn't do it this way. But ok, this is the answer.
Andrey Shchekin
+2  A: 

I don't think it's possible to give a simple answer to this question, because it depends on a lot of different things, including the implementation of IControllerFactory.

For instance, if you have a completely custom-built IControllerFactory implementation, all bets are off, because it may use any sort of mechanism to create Controller instances.

However, the DefaultControllerFactory looks after the appropriate Controller type in all the assemblies defined in the RouteCollection (configured in global.asax).

In this case, you could loop through all the assemblies associated with the RouteCollection, and look for Controllers in each.

Finding Controllers in a given assembly is relatively easy:

var controllerTypes = from t in asm.GetExportedTypes()
                      where typeof(IController).IsAssignableFrom(t)
                      select t;

where asm is an Assembly instance.

Mark Seemann