PageRenderTime 56ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/src/SystemWebMvc/Mvc/ControllerActionInvoker.cs

https://bitbucket.org/markhneedham/aspnet-mvc
C# | 400 lines | 330 code | 56 blank | 14 comment | 71 complexity | 80f1f1d943a9610bc510ac9592d539e0 MD5 | raw file
  1. namespace System.Web.Mvc {
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Threading;
  8. using System.Web;
  9. using System.Web.Mvc.Resources;
  10. [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
  11. [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
  12. public class ControllerActionInvoker : IActionInvoker {
  13. private readonly static ActionMethodDispatcherCache _staticDispatcherCache = new ActionMethodDispatcherCache();
  14. private readonly static ActionMethodSelectorCache _staticSelectorCache = new ActionMethodSelectorCache();
  15. private ActionMethodDispatcherCache _instanceDispatcherCache;
  16. private ActionMethodSelectorCache _instanceSelectorCache;
  17. public ControllerContext ControllerContext {
  18. get;
  19. protected set;
  20. }
  21. internal ActionMethodDispatcherCache DispatcherCache {
  22. get {
  23. if (_instanceDispatcherCache == null) {
  24. _instanceDispatcherCache = _staticDispatcherCache;
  25. }
  26. return _instanceDispatcherCache;
  27. }
  28. set {
  29. _instanceDispatcherCache = value;
  30. }
  31. }
  32. internal ActionMethodSelectorCache SelectorCache {
  33. get {
  34. if (_instanceSelectorCache == null) {
  35. _instanceSelectorCache = _staticSelectorCache;
  36. }
  37. return _instanceSelectorCache;
  38. }
  39. set {
  40. _instanceSelectorCache = value;
  41. }
  42. }
  43. protected virtual ActionResult CreateActionResult(object actionReturnValue) {
  44. if (actionReturnValue == null) {
  45. return new EmptyResult();
  46. }
  47. ActionResult actionResult = (actionReturnValue as ActionResult) ??
  48. new ContentResult { Content = Convert.ToString(actionReturnValue, CultureInfo.InvariantCulture) };
  49. return actionResult;
  50. }
  51. private static object ExtractParameterFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters, MethodInfo methodInfo) {
  52. object value;
  53. bool wasFound = parameters.TryGetValue(parameterInfo.Name, out value);
  54. // The key should always be present, even if the parameter value is null.
  55. if (!wasFound ||
  56. (!(value == null && TypeHelpers.TypeAllowsNullValue(parameterInfo.ParameterType)) &&
  57. !parameterInfo.ParameterType.IsInstanceOfType(value))) {
  58. string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ControllerActionInvoker_ParameterDictionaryContainsInvalidEntry,
  59. parameterInfo.ParameterType, parameterInfo.Name, Convert.ToString(methodInfo, CultureInfo.CurrentUICulture), methodInfo.DeclaringType);
  60. throw new InvalidOperationException(message);
  61. }
  62. return value;
  63. }
  64. private IList<TFilter> FiltersToTypedList<TFilter>(IList<FilterAttribute> filters) where TFilter : class {
  65. List<TFilter> typedList = new List<TFilter>();
  66. // always add the controller (if it's of the right type) first
  67. TFilter controllerFilter = ControllerContext.Controller as TFilter;
  68. if (controllerFilter != null) {
  69. typedList.Add(controllerFilter);
  70. }
  71. // then add filters of the right type
  72. typedList.AddRange(filters.OfType<TFilter>());
  73. return typedList;
  74. }
  75. protected virtual MethodInfo FindActionMethod(string actionName) {
  76. if (String.IsNullOrEmpty(actionName)) {
  77. throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
  78. }
  79. Type controllerType = ControllerContext.Controller.GetType();
  80. ActionMethodSelector selector = SelectorCache.GetSelector(controllerType);
  81. MethodInfo foundMatch = selector.FindActionMethod(ControllerContext, actionName);
  82. if (foundMatch != null) {
  83. if (foundMatch.ContainsGenericParameters) {
  84. throw new InvalidOperationException(String.Format(
  85. CultureInfo.CurrentUICulture, MvcResources.Controller_ActionCannotBeGeneric,
  86. foundMatch));
  87. }
  88. }
  89. return foundMatch;
  90. }
  91. private static string GetFieldPrefix(ParameterInfo parameterInfo) {
  92. BindAttribute attr = (BindAttribute)Attribute.GetCustomAttribute(parameterInfo, typeof(BindAttribute));
  93. return ((attr != null) && (attr.Prefix != null)) ? attr.Prefix : parameterInfo.Name;
  94. }
  95. protected virtual FilterInfo GetFiltersForActionMethod(MethodInfo methodInfo) {
  96. if (methodInfo == null) {
  97. throw new ArgumentNullException("methodInfo");
  98. }
  99. // Enumerable.OrderBy() is a stable sort, so this method preserves scope ordering.
  100. FilterAttribute[] typeFilters = (FilterAttribute[])methodInfo.ReflectedType.GetCustomAttributes(typeof(FilterAttribute), true /* inherit */);
  101. FilterAttribute[] methodFilters = (FilterAttribute[])methodInfo.GetCustomAttributes(typeof(FilterAttribute), true /* inherit */);
  102. List<FilterAttribute> orderedFilters = typeFilters.Concat(methodFilters).OrderBy(attr => attr.Order).ToList();
  103. FilterInfo filterInfo = new FilterInfo {
  104. ActionFilters = FiltersToTypedList<IActionFilter>(orderedFilters),
  105. AuthorizationFilters = FiltersToTypedList<IAuthorizationFilter>(orderedFilters),
  106. ExceptionFilters = FiltersToTypedList<IExceptionFilter>(orderedFilters),
  107. ResultFilters = FiltersToTypedList<IResultFilter>(orderedFilters)
  108. };
  109. return filterInfo;
  110. }
  111. private static IModelBinder GetModelBinder(ParameterInfo parameterInfo) {
  112. // first we look up attributes on the parameter itself
  113. CustomModelBinderAttribute[] attrs = (CustomModelBinderAttribute[])parameterInfo.GetCustomAttributes(typeof(CustomModelBinderAttribute), false /* inherit */);
  114. switch (attrs.Length) {
  115. case 0:
  116. break;
  117. case 1:
  118. IModelBinder converter = attrs[0].GetBinder();
  119. return converter;
  120. default:
  121. throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture,
  122. MvcResources.ControllerActionInvoker_MultipleConverterAttributes,
  123. parameterInfo.Name, parameterInfo.Member.Name));
  124. }
  125. // failing that, we retrieve the global GetConverter() method
  126. IModelBinder globalConverter = ModelBinders.GetBinder(parameterInfo.ParameterType);
  127. return globalConverter;
  128. }
  129. protected virtual object GetParameterValue(ParameterInfo parameterInfo) {
  130. if (parameterInfo == null) {
  131. throw new ArgumentNullException("parameterInfo");
  132. }
  133. Type parameterType = parameterInfo.ParameterType;
  134. IModelBinder converter = GetModelBinder(parameterInfo);
  135. string parameterName = GetFieldPrefix(parameterInfo);
  136. Predicate<string> propertyFilter = GetPropertyFilter(parameterInfo);
  137. ModelBindingContext bindingContext = new ModelBindingContext(ControllerContext, ControllerContext.Controller.ValueProvider, parameterType, parameterName, null /* modelProvider */, ControllerContext.Controller.ViewData.ModelState, propertyFilter);
  138. ModelBinderResult result = converter.BindModel(bindingContext);
  139. return (result != null) ? result.Value : null;
  140. }
  141. protected virtual IDictionary<string, object> GetParameterValues(MethodInfo methodInfo) {
  142. if (methodInfo == null) {
  143. throw new ArgumentNullException("methodInfo");
  144. }
  145. Dictionary<string, object> parameterDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
  146. foreach (ParameterInfo parameterInfo in methodInfo.GetParameters()) {
  147. if (parameterInfo.IsOut || parameterInfo.ParameterType.IsByRef) {
  148. throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_ReferenceParametersNotSupported, parameterInfo.Name, methodInfo.Name));
  149. }
  150. parameterDict[parameterInfo.Name] = GetParameterValue(parameterInfo);
  151. }
  152. return parameterDict;
  153. }
  154. private static Predicate<string> GetPropertyFilter(ParameterInfo parameterInfo) {
  155. BindAttribute attr = (BindAttribute)Attribute.GetCustomAttribute(parameterInfo, typeof(BindAttribute));
  156. return (attr != null) ? (Predicate<string>)attr.IsPropertyAllowed : null;
  157. }
  158. public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) {
  159. if (controllerContext == null) {
  160. throw new ArgumentNullException("controllerContext");
  161. }
  162. if (String.IsNullOrEmpty(actionName)) {
  163. throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
  164. }
  165. ControllerContext = controllerContext;
  166. MethodInfo methodInfo = FindActionMethod(actionName);
  167. if (methodInfo != null) {
  168. IDictionary<string, object> parameters = GetParameterValues(methodInfo);
  169. FilterInfo filterInfo = GetFiltersForActionMethod(methodInfo);
  170. try {
  171. AuthorizationContext authContext = InvokeAuthorizationFilters(methodInfo, filterInfo.AuthorizationFilters);
  172. if (authContext.Cancel) {
  173. // not authorized, so don't execute the action method or its filters
  174. InvokeActionResult(authContext.Result ?? EmptyResult.Instance);
  175. }
  176. else {
  177. ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(methodInfo, parameters, filterInfo.ActionFilters);
  178. InvokeActionResultWithFilters(postActionContext.Result, filterInfo.ResultFilters);
  179. }
  180. }
  181. catch (Exception ex) {
  182. // something blew up, so execute the exception filters
  183. ExceptionContext exceptionContext = InvokeExceptionFilters(ex, filterInfo.ExceptionFilters);
  184. if (!exceptionContext.ExceptionHandled) {
  185. throw;
  186. }
  187. InvokeActionResult(exceptionContext.Result);
  188. }
  189. return true;
  190. }
  191. // notify controller that no method matched
  192. return false;
  193. }
  194. protected virtual ActionResult InvokeActionMethod(MethodInfo methodInfo, IDictionary<string, object> parameters) {
  195. if (methodInfo == null) {
  196. throw new ArgumentNullException("methodInfo");
  197. }
  198. if (parameters == null) {
  199. throw new ArgumentNullException("parameters");
  200. }
  201. ParameterInfo[] parameterInfos = methodInfo.GetParameters();
  202. if (parameterInfos.Length != parameters.Count) {
  203. string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ControllerActionInvoker_ParameterDictionaryCountIncorrect,
  204. methodInfo);
  205. throw new InvalidOperationException(message);
  206. }
  207. object[] parametersArray = (from parameterInfo
  208. in parameterInfos
  209. select ExtractParameterFromDictionary(parameterInfo, parameters, methodInfo)).ToArray();
  210. ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(methodInfo);
  211. object actionReturnValue = dispatcher.Execute(ControllerContext.Controller, parametersArray);
  212. ActionResult actionResult = CreateActionResult(actionReturnValue);
  213. return actionResult;
  214. }
  215. internal static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func<ActionExecutedContext> continuation) {
  216. filter.OnActionExecuting(preContext);
  217. if (preContext.Result != null) {
  218. return new ActionExecutedContext(preContext, true /* canceled */, null /* exception */) {
  219. Result = preContext.Result
  220. };
  221. }
  222. bool wasError = false;
  223. ActionExecutedContext postContext = null;
  224. try {
  225. postContext = continuation();
  226. }
  227. catch (Exception ex) {
  228. wasError = true;
  229. postContext = new ActionExecutedContext(preContext, false /* canceled */, ex);
  230. filter.OnActionExecuted(postContext);
  231. if (!postContext.ExceptionHandled) {
  232. throw;
  233. }
  234. }
  235. if (!wasError) {
  236. filter.OnActionExecuted(postContext);
  237. }
  238. return postContext;
  239. }
  240. protected virtual ActionExecutedContext InvokeActionMethodWithFilters(MethodInfo methodInfo, IDictionary<string, object> parameters, IList<IActionFilter> filters) {
  241. if (methodInfo == null) {
  242. throw new ArgumentNullException("methodInfo");
  243. }
  244. if (parameters == null) {
  245. throw new ArgumentNullException("parameters");
  246. }
  247. if (filters == null) {
  248. throw new ArgumentNullException("filters");
  249. }
  250. ActionExecutingContext preContext = new ActionExecutingContext(ControllerContext, parameters);
  251. Func<ActionExecutedContext> continuation = () =>
  252. new ActionExecutedContext(ControllerContext, false /* canceled */, null /* exception */) {
  253. Result = InvokeActionMethod(methodInfo, parameters)
  254. };
  255. // need to reverse the filter list because the continuations are built up backward
  256. Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation,
  257. (next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));
  258. return thunk();
  259. }
  260. protected virtual void InvokeActionResult(ActionResult actionResult) {
  261. if (actionResult == null) {
  262. throw new ArgumentNullException("actionResult");
  263. }
  264. actionResult.ExecuteResult(ControllerContext);
  265. }
  266. internal static ResultExecutedContext InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func<ResultExecutedContext> continuation) {
  267. filter.OnResultExecuting(preContext);
  268. if (preContext.Cancel) {
  269. return new ResultExecutedContext(preContext, preContext.Result, true /* canceled */, null /* exception */);
  270. }
  271. bool wasError = false;
  272. ResultExecutedContext postContext = null;
  273. try {
  274. postContext = continuation();
  275. }
  276. catch (ThreadAbortException) {
  277. // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
  278. // the filters don't see this as an error.
  279. postContext = new ResultExecutedContext(preContext, preContext.Result, false /* canceled */, null /* exception */);
  280. filter.OnResultExecuted(postContext);
  281. throw;
  282. }
  283. catch (Exception ex) {
  284. wasError = true;
  285. postContext = new ResultExecutedContext(preContext, preContext.Result, false /* canceled */, ex);
  286. filter.OnResultExecuted(postContext);
  287. if (!postContext.ExceptionHandled) {
  288. throw;
  289. }
  290. }
  291. if (!wasError) {
  292. filter.OnResultExecuted(postContext);
  293. }
  294. return postContext;
  295. }
  296. protected virtual ResultExecutedContext InvokeActionResultWithFilters(ActionResult actionResult, IList<IResultFilter> filters) {
  297. if (actionResult == null) {
  298. throw new ArgumentNullException("actionResult");
  299. }
  300. if (filters == null) {
  301. throw new ArgumentNullException("filters");
  302. }
  303. ResultExecutingContext preContext = new ResultExecutingContext(ControllerContext, actionResult);
  304. Func<ResultExecutedContext> continuation = delegate {
  305. InvokeActionResult(actionResult);
  306. return new ResultExecutedContext(ControllerContext, actionResult, false /* canceled */, null /* exception */);
  307. };
  308. // need to reverse the filter list because the continuations are built up backward
  309. Func<ResultExecutedContext> thunk = filters.Reverse().Aggregate(continuation,
  310. (next, filter) => () => InvokeActionResultFilter(filter, preContext, next));
  311. return thunk();
  312. }
  313. protected virtual AuthorizationContext InvokeAuthorizationFilters(MethodInfo methodInfo, IList<IAuthorizationFilter> filters) {
  314. if (methodInfo == null) {
  315. throw new ArgumentNullException("methodInfo");
  316. }
  317. if (filters == null) {
  318. throw new ArgumentNullException("filters");
  319. }
  320. AuthorizationContext context = new AuthorizationContext(ControllerContext);
  321. foreach (IAuthorizationFilter filter in filters) {
  322. filter.OnAuthorization(context);
  323. // short-circuit evaluation
  324. if (context.Cancel) {
  325. break;
  326. }
  327. }
  328. return context;
  329. }
  330. protected virtual ExceptionContext InvokeExceptionFilters(Exception exception, IList<IExceptionFilter> filters) {
  331. if (exception == null) {
  332. throw new ArgumentNullException("exception");
  333. }
  334. if (filters == null) {
  335. throw new ArgumentNullException("filters");
  336. }
  337. ExceptionContext context = new ExceptionContext(ControllerContext, exception);
  338. foreach (IExceptionFilter filter in filters) {
  339. filter.OnException(context);
  340. }
  341. return context;
  342. }
  343. }
  344. }