New tests.
[mono.git] / mcs / class / System.Web.Mvc2 / System.Web.Mvc / ActionDescriptor.cs
1 /* ****************************************************************************\r
2  *\r
3  * Copyright (c) Microsoft Corporation. All rights reserved.\r
4  *\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
8  *\r
9  * You must not remove this notice, or any other, from this software.\r
10  *\r
11  * ***************************************************************************/\r
12 \r
13 namespace System.Web.Mvc {\r
14     using System;\r
15     using System.Collections.Generic;\r
16     using System.Diagnostics.CodeAnalysis;\r
17     using System.Globalization;\r
18     using System.Linq;\r
19     using System.Reflection;\r
20     using System.Web.Mvc.Resources;\r
21 \r
22     public abstract class ActionDescriptor : ICustomAttributeProvider {\r
23 \r
24         private readonly static AllowMultipleAttributesCache _allowMultiplAttributesCache = new AllowMultipleAttributesCache();\r
25         private readonly static ActionMethodDispatcherCache _staticDispatcherCache = new ActionMethodDispatcherCache();\r
26         private ActionMethodDispatcherCache _instanceDispatcherCache;\r
27 \r
28         private static readonly ActionSelector[] _emptySelectors = new ActionSelector[0];\r
29 \r
30         public abstract string ActionName {\r
31             get;\r
32         }\r
33 \r
34         public abstract ControllerDescriptor ControllerDescriptor {\r
35             get;\r
36         }\r
37 \r
38         internal ActionMethodDispatcherCache DispatcherCache {\r
39             get {\r
40                 if (_instanceDispatcherCache == null) {\r
41                     _instanceDispatcherCache = _staticDispatcherCache;\r
42                 }\r
43                 return _instanceDispatcherCache;\r
44             }\r
45             set {\r
46                 _instanceDispatcherCache = value;\r
47             }\r
48         }\r
49 \r
50         public abstract object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);\r
51 \r
52         internal static object ExtractParameterFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters, MethodInfo methodInfo) {\r
53             object value;\r
54 \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
60             }\r
61 \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
67             }\r
68 \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
74             }\r
75 \r
76             return value;\r
77         }\r
78 \r
79         internal static object ExtractParameterOrDefaultFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters) {\r
80             Type parameterType = parameterInfo.ParameterType;\r
81 \r
82             object value;\r
83             parameters.TryGetValue(parameterInfo.Name, out value);\r
84 \r
85             // if wrong type, replace with default instance\r
86             if (parameterType.IsInstanceOfType(value)) {\r
87                 return value;\r
88             }\r
89             else {\r
90                 object defaultValue;\r
91                 if (ParameterInfoUtil.TryGetDefaultValue(parameterInfo, out defaultValue)) {\r
92                     return defaultValue;\r
93                 }\r
94                 else {\r
95                     return TypeHelpers.GetDefaultValue(parameterType);\r
96                 }\r
97             }\r
98         }\r
99 \r
100         public virtual object[] GetCustomAttributes(bool inherit) {\r
101             return GetCustomAttributes(typeof(object), inherit);\r
102         }\r
103 \r
104         public virtual object[] GetCustomAttributes(Type attributeType, bool inherit) {\r
105             if (attributeType == null) {\r
106                 throw new ArgumentNullException("attributeType");\r
107             }\r
108 \r
109             return (object[])Array.CreateInstance(attributeType, 0);\r
110         }\r
111 \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
116         }\r
117 \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
123 \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
129             return filterInfo;\r
130         }\r
131 \r
132         public abstract ParameterDescriptor[] GetParameters();\r
133 \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
138         }\r
139 \r
140         public virtual bool IsDefined(Type attributeType, bool inherit) {\r
141             if (attributeType == null) {\r
142                 throw new ArgumentNullException("attributeType");\r
143             }\r
144 \r
145             return false;\r
146         }\r
147 \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
153                 }\r
154             }\r
155         }\r
156 \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
163 \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
167 \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
172 \r
173                 int lastIndex;\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
179                     }\r
180                 }\r
181                 else {\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
186                 }\r
187             }\r
188 \r
189             // any duplicated attributes have now been nulled out, so just return remaining attributes\r
190             return filtersList.Where(attr => attr != null);\r
191         }\r
192 \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
198             }\r
199 \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
204             }\r
205 \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
212                 }\r
213             }\r
214 \r
215             // we can call this method\r
216             return null;\r
217         }\r
218 \r
219         private sealed class AllowMultipleAttributesCache : ReaderWriterCache<Type, bool> {\r
220             public bool IsMultiUseAttribute(Type attributeType) {\r
221                 return FetchOrCreateItem(attributeType, () => AttributeUsageAllowsMultiple(attributeType));\r
222             }\r
223 \r
224             private static bool AttributeUsageAllowsMultiple(Type type) {\r
225                 return (((AttributeUsageAttribute[])type.GetCustomAttributes(typeof(AttributeUsageAttribute), true))[0]).AllowMultiple;\r
226             }\r
227         }\r
228 \r
229     }\r
230 }\r