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