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.Diagnostics.CodeAnalysis;
\r
17 using System.Globalization;
\r
19 using System.Reflection;
\r
20 using System.Web.Mvc.Resources;
\r
22 public abstract class ActionDescriptor : ICustomAttributeProvider {
\r
24 private readonly static AllowMultipleAttributesCache _allowMultiplAttributesCache = new AllowMultipleAttributesCache();
\r
25 private readonly static ActionMethodDispatcherCache _staticDispatcherCache = new ActionMethodDispatcherCache();
\r
26 private ActionMethodDispatcherCache _instanceDispatcherCache;
\r
28 private static readonly ActionSelector[] _emptySelectors = new ActionSelector[0];
\r
30 public abstract string ActionName {
\r
34 public abstract ControllerDescriptor ControllerDescriptor {
\r
38 internal ActionMethodDispatcherCache DispatcherCache {
\r
40 if (_instanceDispatcherCache == null) {
\r
41 _instanceDispatcherCache = _staticDispatcherCache;
\r
43 return _instanceDispatcherCache;
\r
46 _instanceDispatcherCache = value;
\r
50 public abstract object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);
\r
52 internal static object ExtractParameterFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters, MethodInfo methodInfo) {
\r
55 if (!parameters.TryGetValue(parameterInfo.Name, out value)) {
\r
56 // the key should always be present, even if the parameter value is null
\r
57 string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_ParameterNotInDictionary,
\r
58 parameterInfo.Name, parameterInfo.ParameterType, methodInfo, methodInfo.DeclaringType);
\r
59 throw new ArgumentException(message, "parameters");
\r
62 if (value == null && !TypeHelpers.TypeAllowsNullValue(parameterInfo.ParameterType)) {
\r
63 // tried to pass a null value for a non-nullable parameter type
\r
64 string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_ParameterCannotBeNull,
\r
65 parameterInfo.Name, parameterInfo.ParameterType, methodInfo, methodInfo.DeclaringType);
\r
66 throw new ArgumentException(message, "parameters");
\r
69 if (value != null && !parameterInfo.ParameterType.IsInstanceOfType(value)) {
\r
70 // value was supplied but is not of the proper type
\r
71 string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_ParameterValueHasWrongType,
\r
72 parameterInfo.Name, methodInfo, methodInfo.DeclaringType, value.GetType(), parameterInfo.ParameterType);
\r
73 throw new ArgumentException(message, "parameters");
\r
79 internal static object ExtractParameterOrDefaultFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters) {
\r
80 Type parameterType = parameterInfo.ParameterType;
\r
83 parameters.TryGetValue(parameterInfo.Name, out value);
\r
85 // if wrong type, replace with default instance
\r
86 if (parameterType.IsInstanceOfType(value)) {
\r
90 object defaultValue;
\r
91 if (ParameterInfoUtil.TryGetDefaultValue(parameterInfo, out defaultValue)) {
\r
92 return defaultValue;
\r
95 return TypeHelpers.GetDefaultValue(parameterType);
\r
100 public virtual object[] GetCustomAttributes(bool inherit) {
\r
101 return GetCustomAttributes(typeof(object), inherit);
\r
104 public virtual object[] GetCustomAttributes(Type attributeType, bool inherit) {
\r
105 if (attributeType == null) {
\r
106 throw new ArgumentNullException("attributeType");
\r
109 return (object[])Array.CreateInstance(attributeType, 0);
\r
112 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
\r
113 Justification = "This method may perform non-trivial work.")]
\r
114 public virtual FilterInfo GetFilters() {
\r
115 return new FilterInfo();
\r
118 internal static FilterInfo GetFilters(MethodInfo methodInfo) {
\r
119 // Enumerable.OrderBy() is a stable sort, so this method preserves scope ordering.
\r
120 FilterAttribute[] typeFilters = (FilterAttribute[])methodInfo.ReflectedType.GetCustomAttributes(typeof(FilterAttribute), true /* inherit */);
\r
121 FilterAttribute[] methodFilters = (FilterAttribute[])methodInfo.GetCustomAttributes(typeof(FilterAttribute), true /* inherit */);
\r
122 List<FilterAttribute> orderedFilters = RemoveOverriddenFilters(typeFilters.Concat(methodFilters)).OrderBy(attr => attr.Order).ToList();
\r
124 FilterInfo filterInfo = new FilterInfo();
\r
125 MergeFiltersIntoList(orderedFilters, filterInfo.ActionFilters);
\r
126 MergeFiltersIntoList(orderedFilters, filterInfo.AuthorizationFilters);
\r
127 MergeFiltersIntoList(orderedFilters, filterInfo.ExceptionFilters);
\r
128 MergeFiltersIntoList(orderedFilters, filterInfo.ResultFilters);
\r
132 public abstract ParameterDescriptor[] GetParameters();
\r
134 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
\r
135 Justification = "This method may perform non-trivial work.")]
\r
136 public virtual ICollection<ActionSelector> GetSelectors() {
\r
137 return _emptySelectors;
\r
140 public virtual bool IsDefined(Type attributeType, bool inherit) {
\r
141 if (attributeType == null) {
\r
142 throw new ArgumentNullException("attributeType");
\r
148 internal static void MergeFiltersIntoList<TFilter>(IList<FilterAttribute> allFilters, IList<TFilter> destFilters) where TFilter : class {
\r
149 foreach (FilterAttribute filter in allFilters) {
\r
150 TFilter castFilter = filter as TFilter;
\r
151 if (castFilter != null) {
\r
152 destFilters.Add(castFilter);
\r
157 internal static IEnumerable<FilterAttribute> RemoveOverriddenFilters(IEnumerable<FilterAttribute> filters) {
\r
158 // If an attribute is declared on both the controller and on an action method and that attribute's
\r
159 // type has AllowMultiple = false (which is the default for attributes), we should ignore the attributes
\r
160 // declared on the controller. The CLR's reflection implementation follows a similar algorithm when it
\r
161 // encounters an overridden virtual method where both the base and the override contain some
\r
162 // AllowMultiple = false attribute.
\r
164 // Key = attribute type
\r
165 // Value = -1 if AllowMultiple true, last index of this attribute type if AllowMultiple false
\r
166 Dictionary<Type, int> attrsIndexes = new Dictionary<Type, int>();
\r
168 FilterAttribute[] filtersList = filters.ToArray();
\r
169 for (int i = 0; i < filtersList.Length; i++) {
\r
170 FilterAttribute filter = filtersList[i];
\r
171 Type filterType = filter.GetType();
\r
174 if (attrsIndexes.TryGetValue(filterType, out lastIndex)) {
\r
175 if (lastIndex >= 0) {
\r
176 // this filter already exists and AllowMultiple = false, so clear last entry
\r
177 filtersList[lastIndex] = null;
\r
178 attrsIndexes[filterType] = i;
\r
182 // not found - add to dictionary
\r
183 // exactly one AttributeUsageAttribute will always be present
\r
184 bool allowMultiple = _allowMultiplAttributesCache.IsMultiUseAttribute(filterType);
\r
185 attrsIndexes[filterType] = (allowMultiple) ? -1 : i;
\r
189 // any duplicated attributes have now been nulled out, so just return remaining attributes
\r
190 return filtersList.Where(attr => attr != null);
\r
193 internal static string VerifyActionMethodIsCallable(MethodInfo methodInfo) {
\r
194 // we can't call instance methods where the 'this' parameter is a type other than ControllerBase
\r
195 if (!methodInfo.IsStatic && !typeof(ControllerBase).IsAssignableFrom(methodInfo.ReflectedType)) {
\r
196 return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallInstanceMethodOnNonControllerType,
\r
197 methodInfo, methodInfo.ReflectedType.FullName);
\r
200 // we can't call methods with open generic type parameters
\r
201 if (methodInfo.ContainsGenericParameters) {
\r
202 return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallOpenGenericMethods,
\r
203 methodInfo, methodInfo.ReflectedType.FullName);
\r
206 // we can't call methods with ref/out parameters
\r
207 ParameterInfo[] parameterInfos = methodInfo.GetParameters();
\r
208 foreach (ParameterInfo parameterInfo in parameterInfos) {
\r
209 if (parameterInfo.IsOut || parameterInfo.ParameterType.IsByRef) {
\r
210 return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallMethodsWithOutOrRefParameters,
\r
211 methodInfo, methodInfo.ReflectedType.FullName, parameterInfo);
\r
215 // we can call this method
\r
219 private sealed class AllowMultipleAttributesCache : ReaderWriterCache<Type, bool> {
\r
220 public bool IsMultiUseAttribute(Type attributeType) {
\r
221 return FetchOrCreateItem(attributeType, () => AttributeUsageAllowsMultiple(attributeType));
\r
224 private static bool AttributeUsageAllowsMultiple(Type type) {
\r
225 return (((AttributeUsageAttribute[])type.GetCustomAttributes(typeof(AttributeUsageAttribute), true))[0]).AllowMultiple;
\r