fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
3 minut

Typ kontrolera dla DOWOLNEGO request w asp mvc 3


21.07.2011

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.

0 0 votes
Article Rating
5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
M.
M.
12 years ago

[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 */);
        }
procent
12 years ago

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?

Gorallo
Gorallo
12 years ago

@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 :)

jdubrownik
jdubrownik
12 years ago

@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ą".

jdubrownik
jdubrownik
12 years ago

@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...

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również