New tests.
[mono.git] / mcs / class / System.Web.Mvc2 / System.Web.Mvc / ActionMethodSelector.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.Globalization;\r
17     using System.Linq;\r
18     using System.Reflection;\r
19     using System.Text;\r
20     using System.Web.Mvc.Resources;\r
21 \r
22     internal sealed class ActionMethodSelector {\r
23 \r
24         public ActionMethodSelector(Type controllerType) {\r
25             ControllerType = controllerType;\r
26             PopulateLookupTables();\r
27         }\r
28 \r
29         public Type ControllerType {\r
30             get;\r
31             private set;\r
32         }\r
33 \r
34         public MethodInfo[] AliasedMethods {\r
35             get;\r
36             private set;\r
37         }\r
38 \r
39         public ILookup<string, MethodInfo> NonAliasedMethods {\r
40             get;\r
41             private set;\r
42         }\r
43 \r
44         private AmbiguousMatchException CreateAmbiguousMatchException(List<MethodInfo> ambiguousMethods, string actionName) {\r
45             StringBuilder exceptionMessageBuilder = new StringBuilder();\r
46             foreach (MethodInfo methodInfo in ambiguousMethods) {\r
47                 string controllerAction = Convert.ToString(methodInfo, CultureInfo.CurrentUICulture);\r
48                 string controllerType = methodInfo.DeclaringType.FullName;\r
49                 exceptionMessageBuilder.AppendLine();\r
50                 exceptionMessageBuilder.AppendFormat(CultureInfo.CurrentUICulture, MvcResources.ActionMethodSelector_AmbiguousMatchType, controllerAction, controllerType);\r
51             }\r
52             string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ActionMethodSelector_AmbiguousMatch,\r
53                 actionName, ControllerType.Name, exceptionMessageBuilder);\r
54             return new AmbiguousMatchException(message);\r
55         }\r
56 \r
57         public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName) {\r
58             List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName);\r
59             methodsMatchingName.AddRange(NonAliasedMethods[actionName]);\r
60             List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName);\r
61 \r
62             switch (finalMethods.Count) {\r
63                 case 0:\r
64                     return null;\r
65 \r
66                 case 1:\r
67                     return finalMethods[0];\r
68 \r
69                 default:\r
70                     throw CreateAmbiguousMatchException(finalMethods, actionName);\r
71             }\r
72         }\r
73 \r
74         internal List<MethodInfo> GetMatchingAliasedMethods(ControllerContext controllerContext, string actionName) {\r
75             // find all aliased methods which are opting in to this request\r
76             // to opt in, all attributes defined on the method must return true\r
77 \r
78             var methods = from methodInfo in AliasedMethods\r
79                           let attrs = (ActionNameSelectorAttribute[])methodInfo.GetCustomAttributes(typeof(ActionNameSelectorAttribute), true /* inherit */)\r
80                           where attrs.All(attr => attr.IsValidName(controllerContext, actionName, methodInfo))\r
81                           select methodInfo;\r
82             return methods.ToList();\r
83         }\r
84 \r
85         private static bool IsMethodDecoratedWithAliasingAttribute(MethodInfo methodInfo) {\r
86             return methodInfo.IsDefined(typeof(ActionNameSelectorAttribute), true /* inherit */);\r
87         }\r
88 \r
89         private static bool IsValidActionMethod(MethodInfo methodInfo) {\r
90             return !(methodInfo.IsSpecialName ||\r
91                      methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(typeof(Controller)));\r
92         }\r
93 \r
94         private void PopulateLookupTables() {\r
95             MethodInfo[] allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);\r
96             MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethod);\r
97 \r
98             AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute);\r
99             NonAliasedMethods = actionMethods.Except(AliasedMethods).ToLookup(method => method.Name, StringComparer.OrdinalIgnoreCase);\r
100         }\r
101 \r
102         private static List<MethodInfo> RunSelectionFilters(ControllerContext controllerContext, List<MethodInfo> methodInfos) {\r
103             // remove all methods which are opting out of this request\r
104             // to opt out, at least one attribute defined on the method must return false\r
105 \r
106             List<MethodInfo> matchesWithSelectionAttributes = new List<MethodInfo>();\r
107             List<MethodInfo> matchesWithoutSelectionAttributes = new List<MethodInfo>();\r
108 \r
109             foreach (MethodInfo methodInfo in methodInfos) {\r
110                 ActionMethodSelectorAttribute[] attrs = (ActionMethodSelectorAttribute[])methodInfo.GetCustomAttributes(typeof(ActionMethodSelectorAttribute), true /* inherit */);\r
111                 if (attrs.Length == 0) {\r
112                     matchesWithoutSelectionAttributes.Add(methodInfo);\r
113                 }\r
114                 else if (attrs.All(attr => attr.IsValidForRequest(controllerContext, methodInfo))) {\r
115                     matchesWithSelectionAttributes.Add(methodInfo);\r
116                 }\r
117             }\r
118 \r
119             // if a matching action method had a selection attribute, consider it more specific than a matching action method\r
120             // without a selection attribute\r
121             return (matchesWithSelectionAttributes.Count > 0) ? matchesWithSelectionAttributes : matchesWithoutSelectionAttributes;\r
122         }\r
123 \r
124     }\r
125 }\r