Project-wide controller survey through reflection

I often lose track of all of the different controllers in our system, especially if I’m trying to see what existing conventions we have in place for the design of actions.  To get around this, I use a simple LINQ query to display all of the controllers and actions in our system in an easily readable format:

var controllers =
    from t in GetAllControllerTypes()
    where typeof(Controller).IsAssignableFrom(t) && !t.IsAbstract
    orderby t.FullName
    from m in t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
    where !m.IsSpecialName
    select new { ControllerName = FormatControllerName(t.FullName), ActionName = m.Name, Params = m.GetParameters() };

controllers.Each(c => Debug.WriteLine("Controller: " + c.ControllerName + ", Action: " + c.ActionName + "(" + string.Join(", ", c.Params.Select(p => p.Name).ToArray()) + ")"));
Debug.WriteLine("Controller/action count: " + controllers.Count());
Debug.WriteLine("Controller count: " + controllers.Distinct(c => c.ControllerName).Count());

The above LINQ query combines the controller name with each action, showing the action parameter names in a readable format.  The GetAllControllerTypes is just a method that returns all types in an assembly where my controllers can be found:

private static Type[] GetAllControllerTypes()
{
    return typeof(ProductController).Assembly.GetTypes();
}

Finally, I like to remove all of the namespace information from the controller names, but keep any potential area information (i.e., get rid of the root namespace, but keep any sub-namespaces:

private static string FormatControllerName(string typeName)
{
    return typeName.Replace("MvcApplication2.", string.Empty).Replace("Controllers.", string.Empty);
}

When I run this against a sample application provided by the MVC default project:

Controller: AccountController, Action: LogOn()
Controller: AccountController, Action: LogOn(userName, password, rememberMe, returnUrl)
Controller: AccountController, Action: LogOff()
Controller: AccountController, Action: Register()
Controller: AccountController, Action: Register(userName, email, password, confirmPassword)
Controller: AccountController, Action: ChangePassword()
Controller: AccountController, Action: ChangePassword(currentPassword, newPassword, confirmPassword)
Controller: AccountController, Action: ChangePasswordSuccess()
Controller: HomeController, Action: Index()
Controller: HomeController, Action: About()
Controller: ProductController, Action: Index()
Controller: ProductController, Action: Details(id)
Controller: ProductController, Action: Create()
Controller: ProductController, Action: Create(product)
Controller: ProductController, Action: Edit(id)
Controller: ProductController, Action: Edit(id, product)
Controller/action count: 16
Controller count: 3

The cool thing about LINQ against reflection is it lets you get these interesting surveys of your system.  When you start filtering it against base types, you can see a nice view of your system that things like ReSharper and other tools aren’t easily massaged.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC, LINQ. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Rob Scott

    Nice.

  • http://www.agilification.com Jeff Doolittle

    Might be worth taking a look at NDepend.

  • http://blog.robustsoftware.co.uk Garry Shutler

    I like it. What would be even cooler (haven’t worked out in my head how you’d achieve it) would be if you could generate the URL for each of the actions as well.

  • http://www.teamdatalogic.com Depaulo

    Awesome! A problem we get sometimes is that when a domain class is made abstract, suddenly actions fail as the model binder can no longer instantiate the class. I have used your code here to create a test which runs through all our action method parameter types and try to instantiate them. Now we will never have the problem again! Hurrah!

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Jeff

    That’s something I thought about…but since this code lives in an explicit unit test (my favorite sandbox), it’s only a few keystrokes away inside VS.

    @Depaulo

    Oh yeah, we have another test that goes through our actions and finds “bad” ones. You might be able to get that too with the Type.IsAbstract and doing another Where.

  • http://drozdyuk.blogspot.com drozzy

    Just gota love the microsoft convention of naming obvious things in obvious ways:
    Controller AccountController;

    Why not go all the way and call a string for what it is? :-)
    String first_name_string;

  • Marko Mijuskovic

    You’re the man!!! Just used this to get the same info for our project…