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
{
15 using System
.Collections
.Generic
;
16 using System
.Globalization
;
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");
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
{
63 public override ControllerDescriptor ControllerDescriptor
{
65 return _controllerDescriptor
;
69 internal ActionMethodDispatcherCache DispatcherCache
{
71 if (_instanceDispatcherCache
== null) {
72 _instanceDispatcherCache
= _staticDispatcherCache
;
74 return _instanceDispatcherCache
;
77 _instanceDispatcherCache
= value;
81 public MethodInfo MethodInfo
{
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
) {
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");
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
);
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
)));
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