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.

Notify of
M.
M.

[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 =… Read more »

procent

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

@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

@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

@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

Facebook

Książka

Zobacz również