Typ kontrolera dla DOWOLNEGO request w asp mvc 3

5

Zdarzają się sytuacje (jakie – o tym niedługo) w których przydałoby się dostać informację o tym, który kontroler zajmie się przetwarzaniem żądania. Niestety takie dane są dość ukryte we flakach MVC. Zrozumiałe jest, że taka logika jest częścią frameworka – w końcu to właśnie framework jest odpowiedzialny za utworzenie kontrolera na podstawie danych wysłanych z przeglądarki – ale dlaczego od razu chować tą logikę za jakimiś "internal"?

Standardowo, o ile nie chcemy wpinać się w proces tworzenia kontrolera, wykorzystywana jest do tego celu klasa DefaultControllerFactory z metodą CreateController. Łatwo zauważyć, że klasa ta ma także metodę GetControllerType, która mnie interesuje. Ale niestety programiści w MS, jak to mają w zwyczaju, nadali jej atrybuty protected internal.

Jak widać, trochę kodu w niej siedzi:

getcontrollertype

Można go przekleić (np z megacoolerskiego dotPeek) do swojego projektu, ale… po co?

Oszukamy MS!

Najpierw napiszemy własną fabrykę kontrolerów. Nie po to, żeby coś zmieniać, ale po to aby udostępnić wspomnianą metodę na zewnątrz:

  1:  public class MyControllerFactory : DefaultControllerFactory
  2:  {
  3:      /// <summary>
  4:      /// Opens base method for external use
  5:      /// </summary>
  6:      public Type FindControllerType(RequestContext requestContext, string controllerName)
  7:      {
  8:          return base.GetControllerType(requestContext, controllerName);
  9:      }
 10:  }

Potem powiemy MVC, że ma użyć tej a nie innej klasy do znajdowania kontrolerów (w global.asax.cs):

  1:  public override void Init()
  2:  {
  3:      base.Init();
  4:      ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());
  5:  }

I już.

Dodatkowo napisałem sobie taki util:

  1:  public static class ControllerTypeLocator
  2:  {
  3:      private static List<Type> _allControllers;
  4:      private static readonly object _syncRoot = new object();
  5:  
  6:      private static void DiscoverAllControllers()
  7:      {
  8:          if (_allControllers == null)
  9:          {
 10:              lock (_syncRoot)
 11:              {
 12:                  if (_allControllers == null)
 13:                  {
 14:                      var allTypes = AppDomain.CurrentDomain.GetMyAppTypes();
 15:                      _allControllers = allTypes
 16:                          .Where(t => t.CanBeInstantiated() && t.IsAssignableTo<Controller>())
 17:                          .ToList();
 18:                  }
 19:              }
 20:          }
 21:      }
 22:  
 23:      public static Type FindControllerType(RequestContext requestContext, string controllerName)
 24:      {
 25:          var controllerFactory = ControllerBuilder.Current.GetControllerFactory()
 26:              as MyControllerFactory;
 27:  
 28:          Type controllerType = controllerFactory.FindControllerType(requestContext, controllerName);
 29:  
 30:          if (controllerType == null)
 31:          {
 32:              // if controller factory did not find a controller, try brute force:
 33:              // scan all discovered controllers
 34:              DiscoverAllControllers();
 35:              controllerType = _allControllers.Where(x => x.Name == controllerName + "Controller")
 36:                  .Single();
 37:          }
 38:  
 39:          return controllerType;
 40:      }
 41:  }

Nie jest to na pewno jakaś super-wyrafinowana technika, ale spełnia swoje zadanie – przebija się przez MSową gardę internali.

Share.

About Author

Programista, trener, prelegent, pasjonat, blogger. Autor podcasta programistycznego: DevTalk.pl. Jeden z liderów Białostockiej Grupy .NET i współorganizator konferencji Programistok. Od 2008 Microsoft MVP w kategorii .NET. Więcej informacji znajdziesz na stronie O autorze. Napisz do mnie ze strony Kontakt. Dodatkowo: Twitter, Facebook, YouTube.

5 Comments

  1. [quote]Można go przekleić (np z megacoolerskiego dotPeek) do swojego projektu[/quote]
    A nie prościej byłoby zajrzeć po prostu do kodu http://aspnet.codeplex.com/SourceControl/changeset/view/68345#266459

            protected internal virtual Type GetControllerType(string controllerName) {
                if (String.IsNullOrEmpty(controllerName)) {
                    throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
                }
    
                // first search in the current route's namespace collection
                object routeNamespacesObj;
                Type match;
                if (RequestContext != null && RequestContext.RouteData.DataTokens.TryGetValue("Namespaces", out routeNamespacesObj)) {
                    IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>;
                    if (routeNamespaces != null) {
                        HashSet<string> nsHash = new HashSet<string>(routeNamespaces, StringComparer.OrdinalIgnoreCase);
                        match = GetControllerTypeWithinNamespaces(controllerName, nsHash);
                        if (match != null) {
                            return match;
                        }
                    }
                }
    
                // then search in the application's default namespace collection
                HashSet<string> nsDefaults = new HashSet<string>(ControllerBuilder.DefaultNamespaces, StringComparer.OrdinalIgnoreCase);
                match = GetControllerTypeWithinNamespaces(controllerName, nsDefaults);
                if (match != null) {
                    return match;
                }
    
                // if all else fails, search every namespace
                return GetControllerTypeWithinNamespaces(controllerName, null /* namespaces */);
            }
    
  2. M,
    Moim zdaniem nie prościej – w reflectorze/dotPeek/czymkolwiek wygodniej mi się bada interesujące klasy w zewnętrznych bibliotekach, nie muszę szukać w necie źródła a w źródle odpowiedniej klasy. Bo po co skoro narzędzie zrobi to za mnie?

  3. @M
    Autor ma rację. Łatwiej jest podejrzeć źródła z dll (chodź to dla niektórych wiedza tajemna – ilspy, dotPeek, Reflector), niż szukanie źródeł gdzieś w internecie. Sam tak często robię i z doświadczenia wiem że można znaleźć sporo fajnego kodu :)

  4. @procent

    var controllerFactory = ControllerBuilder.Current.GetControllerFactory() as MyControllerFactory nie zadziała przy włączony Glimpse, który wstrzykuje własną fabrykę kontrolerów i "nadpisuje naszą".

  5. @procent up

    Zapomniałem dodać kodu rozwiązania. :D

    var controllerFactory = ControllerBuilder.Current.GetControllerFactory() as MyControllerFactory;
    var propertyInfo = controllerFactory.GetType().GetProperty("ControllerFactory");
    if(propertyInfo != null)
    {
    controllerFactory = propertyInfo.GetValue(factory, null) as MyControllerFactory;
    }
    
    // tutaj jeżeli controllerFactory jest null to jakiś exception...