1 /* ****************************************************************************
\r
3 * Copyright (c) Microsoft Corporation. All rights reserved.
\r
5 * This software is subject to the Microsoft Public License (Ms-PL).
\r
6 * A copy of the license can be found in the license.htm file included
\r
7 * in this distribution.
\r
9 * You must not remove this notice, or any other, from this software.
\r
11 * ***************************************************************************/
\r
13 namespace System.Web.Mvc {
\r
15 using System.Collections.Generic;
\r
16 using System.Globalization;
\r
18 using System.Reflection;
\r
19 using System.Web.Mvc.Resources;
\r
21 public class ReflectedActionDescriptor : ActionDescriptor {
\r
23 private readonly static ActionMethodDispatcherCache _staticDispatcherCache = new ActionMethodDispatcherCache();
\r
24 private ActionMethodDispatcherCache _instanceDispatcherCache;
\r
26 private readonly string _actionName;
\r
27 private readonly ControllerDescriptor _controllerDescriptor;
\r
28 private ParameterDescriptor[] _parametersCache;
\r
30 public ReflectedActionDescriptor(MethodInfo methodInfo, string actionName, ControllerDescriptor controllerDescriptor)
\r
31 : this(methodInfo, actionName, controllerDescriptor, true /* validateMethod */) {
\r
34 internal ReflectedActionDescriptor(MethodInfo methodInfo, string actionName, ControllerDescriptor controllerDescriptor, bool validateMethod) {
\r
35 if (methodInfo == null) {
\r
36 throw new ArgumentNullException("methodInfo");
\r
38 if (String.IsNullOrEmpty(actionName)) {
\r
39 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
\r
41 if (controllerDescriptor == null) {
\r
42 throw new ArgumentNullException("controllerDescriptor");
\r
45 if (validateMethod) {
\r
46 string failedMessage = VerifyActionMethodIsCallable(methodInfo);
\r
47 if (failedMessage != null) {
\r
48 throw new ArgumentException(failedMessage, "methodInfo");
\r
52 MethodInfo = methodInfo;
\r
53 _actionName = actionName;
\r
54 _controllerDescriptor = controllerDescriptor;
\r
57 public override string ActionName {
\r
63 public override ControllerDescriptor ControllerDescriptor {
\r
65 return _controllerDescriptor;
\r
69 internal ActionMethodDispatcherCache DispatcherCache {
\r
71 if (_instanceDispatcherCache == null) {
\r
72 _instanceDispatcherCache = _staticDispatcherCache;
\r
74 return _instanceDispatcherCache;
\r
77 _instanceDispatcherCache = value;
\r
81 public MethodInfo MethodInfo {
\r
86 public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters) {
\r
87 if (controllerContext == null) {
\r
88 throw new ArgumentNullException("controllerContext");
\r
90 if (parameters == null) {
\r
91 throw new ArgumentNullException("parameters");
\r
94 ParameterInfo[] parameterInfos = MethodInfo.GetParameters();
\r
95 var rawParameterValues = from parameterInfo in parameterInfos
\r
96 select ExtractParameterFromDictionary(parameterInfo, parameters, MethodInfo);
\r
97 object[] parametersArray = rawParameterValues.ToArray();
\r
99 ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(MethodInfo);
\r
100 object actionReturnValue = dispatcher.Execute(controllerContext.Controller, parametersArray);
\r
101 return actionReturnValue;
\r
104 private static object ExtractParameterFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters, MethodInfo methodInfo) {
\r
107 if (!parameters.TryGetValue(parameterInfo.Name, out value)) {
\r
108 // the key should always be present, even if the parameter value is null
\r
109 string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_ParameterNotInDictionary,
\r
110 parameterInfo.Name, parameterInfo.ParameterType, methodInfo, methodInfo.DeclaringType);
\r
111 throw new ArgumentException(message, "parameters");
\r
114 if (value == null && !TypeHelpers.TypeAllowsNullValue(parameterInfo.ParameterType)) {
\r
115 // tried to pass a null value for a non-nullable parameter type
\r
116 string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_ParameterCannotBeNull,
\r
117 parameterInfo.Name, parameterInfo.ParameterType, methodInfo, methodInfo.DeclaringType);
\r
118 throw new ArgumentException(message, "parameters");
\r
121 if (value != null && !parameterInfo.ParameterType.IsInstanceOfType(value)) {
\r
122 // value was supplied but is not of the proper type
\r
123 string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_ParameterValueHasWrongType,
\r
124 parameterInfo.Name, methodInfo, methodInfo.DeclaringType, value.GetType(), parameterInfo.ParameterType);
\r
125 throw new ArgumentException(message, "parameters");
\r
131 public override object[] GetCustomAttributes(bool inherit) {
\r
132 return MethodInfo.GetCustomAttributes(inherit);
\r
135 public override object[] GetCustomAttributes(Type attributeType, bool inherit) {
\r
136 return MethodInfo.GetCustomAttributes(attributeType, inherit);
\r
139 public override FilterInfo GetFilters() {
\r
140 // Enumerable.OrderBy() is a stable sort, so this method preserves scope ordering.
\r
141 FilterAttribute[] typeFilters = (FilterAttribute[])MethodInfo.ReflectedType.GetCustomAttributes(typeof(FilterAttribute), true /* inherit */);
\r
142 FilterAttribute[] methodFilters = (FilterAttribute[])MethodInfo.GetCustomAttributes(typeof(FilterAttribute), true /* inherit */);
\r
143 List<FilterAttribute> orderedFilters = typeFilters.Concat(methodFilters).OrderBy(attr => attr.Order).ToList();
\r
145 FilterInfo filterInfo = new FilterInfo();
\r
146 MergeFiltersIntoList(orderedFilters, filterInfo.ActionFilters);
\r
147 MergeFiltersIntoList(orderedFilters, filterInfo.AuthorizationFilters);
\r
148 MergeFiltersIntoList(orderedFilters, filterInfo.ExceptionFilters);
\r
149 MergeFiltersIntoList(orderedFilters, filterInfo.ResultFilters);
\r
153 public override ParameterDescriptor[] GetParameters() {
\r
154 ParameterDescriptor[] parameters = LazilyFetchParametersCollection();
\r
156 // need to clone array so that user modifications aren't accidentally stored
\r
157 return (ParameterDescriptor[])parameters.Clone();
\r
160 public override ICollection<ActionSelector> GetSelectors() {
\r
161 ActionMethodSelectorAttribute[] attrs = (ActionMethodSelectorAttribute[])MethodInfo.GetCustomAttributes(typeof(ActionMethodSelectorAttribute), true /* inherit */);
\r
162 ActionSelector[] selectors = Array.ConvertAll(attrs, attr => (ActionSelector)(controllerContext => attr.IsValidForRequest(controllerContext, MethodInfo)));
\r
166 public override bool IsDefined(Type attributeType, bool inherit) {
\r
167 return MethodInfo.IsDefined(attributeType, inherit);
\r
170 private ParameterDescriptor[] LazilyFetchParametersCollection() {
\r
171 return DescriptorUtil.LazilyFetchOrCreateDescriptors<ParameterInfo, ParameterDescriptor>(
\r
172 ref _parametersCache /* cacheLocation */,
\r
173 MethodInfo.GetParameters /* initializer */,
\r
174 parameterInfo => new ReflectedParameterDescriptor(parameterInfo, this) /* converter */);
\r
177 private static void MergeFiltersIntoList<TFilter>(IList<FilterAttribute> allFilters, IList<TFilter> destFilters) where TFilter : class {
\r
178 foreach (FilterAttribute filter in allFilters) {
\r
179 TFilter castFilter = filter as TFilter;
\r
180 if (castFilter != null) {
\r
181 destFilters.Add(castFilter);
\r
186 internal static ReflectedActionDescriptor TryCreateDescriptor(MethodInfo methodInfo, string name, ControllerDescriptor controllerDescriptor) {
\r
187 ReflectedActionDescriptor descriptor = new ReflectedActionDescriptor(methodInfo, name, controllerDescriptor, false /* validateMethod */);
\r
188 string failedMessage = VerifyActionMethodIsCallable(methodInfo);
\r
189 return (failedMessage == null) ? descriptor : null;
\r
192 private static string VerifyActionMethodIsCallable(MethodInfo methodInfo) {
\r
193 // we can't call instance methods where the 'this' parameter is a type other than ControllerBase
\r
194 if (!methodInfo.IsStatic && !typeof(ControllerBase).IsAssignableFrom(methodInfo.ReflectedType)) {
\r
195 return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallInstanceMethodOnNonControllerType,
\r
196 methodInfo, methodInfo.ReflectedType.FullName);
\r
199 // we can't call methods with open generic type parameters
\r
200 if (methodInfo.ContainsGenericParameters) {
\r
201 return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallOpenGenericMethods,
\r
202 methodInfo, methodInfo.ReflectedType.FullName);
\r
205 // we can't call methods with ref/out parameters
\r
206 ParameterInfo[] parameterInfos = methodInfo.GetParameters();
\r
207 foreach (ParameterInfo parameterInfo in parameterInfos) {
\r
208 if (parameterInfo.IsOut || parameterInfo.ParameterType.IsByRef) {
\r
209 return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallMethodsWithOutOrRefParameters,
\r
210 methodInfo, methodInfo.ReflectedType.FullName, parameterInfo);
\r
214 // we can call this method
\r