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:
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.
[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
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?
@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 :)
@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ą".
@procent up
Zapomniałem dodać kodu rozwiązania. :D