1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
|
/* ****************************************************************************
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* This software is subject to the Microsoft Public License (Ms-PL).
* A copy of the license can be found in the license.htm file included
* in this distribution.
*
* You must not remove this notice, or any other, from this software.
*
* ***************************************************************************/
namespace System.Web.Mvc {
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Web.Mvc.Resources;
public abstract class ActionDescriptor : ICustomAttributeProvider {
private readonly static AllowMultipleAttributesCache _allowMultiplAttributesCache = new AllowMultipleAttributesCache();
private readonly static ActionMethodDispatcherCache _staticDispatcherCache = new ActionMethodDispatcherCache();
private ActionMethodDispatcherCache _instanceDispatcherCache;
private static readonly ActionSelector[] _emptySelectors = new ActionSelector[0];
public abstract string ActionName {
get;
}
public abstract ControllerDescriptor ControllerDescriptor {
get;
}
internal ActionMethodDispatcherCache DispatcherCache {
get {
if (_instanceDispatcherCache == null) {
_instanceDispatcherCache = _staticDispatcherCache;
}
return _instanceDispatcherCache;
}
set {
_instanceDispatcherCache = value;
}
}
public abstract object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);
internal static object ExtractParameterFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters, MethodInfo methodInfo) {
object value;
if (!parameters.TryGetValue(parameterInfo.Name, out value)) {
// the key should always be present, even if the parameter value is null
string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_ParameterNotInDictionary,
parameterInfo.Name, parameterInfo.ParameterType, methodInfo, methodInfo.DeclaringType);
throw new ArgumentException(message, "parameters");
}
if (value == null && !TypeHelpers.TypeAllowsNullValue(parameterInfo.ParameterType)) {
// tried to pass a null value for a non-nullable parameter type
string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_ParameterCannotBeNull,
parameterInfo.Name, parameterInfo.ParameterType, methodInfo, methodInfo.DeclaringType);
throw new ArgumentException(message, "parameters");
}
if (value != null && !parameterInfo.ParameterType.IsInstanceOfType(value)) {
// value was supplied but is not of the proper type
string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_ParameterValueHasWrongType,
parameterInfo.Name, methodInfo, methodInfo.DeclaringType, value.GetType(), parameterInfo.ParameterType);
throw new ArgumentException(message, "parameters");
}
return value;
}
internal static object ExtractParameterOrDefaultFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters) {
Type parameterType = parameterInfo.ParameterType;
object value;
parameters.TryGetValue(parameterInfo.Name, out value);
// if wrong type, replace with default instance
if (parameterType.IsInstanceOfType(value)) {
return value;
}
else {
object defaultValue;
if (ParameterInfoUtil.TryGetDefaultValue(parameterInfo, out defaultValue)) {
return defaultValue;
}
else {
return TypeHelpers.GetDefaultValue(parameterType);
}
}
}
public virtual object[] GetCustomAttributes(bool inherit) {
return GetCustomAttributes(typeof(object), inherit);
}
public virtual object[] GetCustomAttributes(Type attributeType, bool inherit) {
if (attributeType == null) {
throw new ArgumentNullException("attributeType");
}
return (object[])Array.CreateInstance(attributeType, 0);
}
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
Justification = "This method may perform non-trivial work.")]
public virtual FilterInfo GetFilters() {
return new FilterInfo();
}
internal static FilterInfo GetFilters(MethodInfo methodInfo) {
// Enumerable.OrderBy() is a stable sort, so this method preserves scope ordering.
FilterAttribute[] typeFilters = (FilterAttribute[])methodInfo.ReflectedType.GetCustomAttributes(typeof(FilterAttribute), true /* inherit */);
FilterAttribute[] methodFilters = (FilterAttribute[])methodInfo.GetCustomAttributes(typeof(FilterAttribute), true /* inherit */);
List<FilterAttribute> orderedFilters = RemoveOverriddenFilters(typeFilters.Concat(methodFilters)).OrderBy(attr => attr.Order).ToList();
FilterInfo filterInfo = new FilterInfo();
MergeFiltersIntoList(orderedFilters, filterInfo.ActionFilters);
MergeFiltersIntoList(orderedFilters, filterInfo.AuthorizationFilters);
MergeFiltersIntoList(orderedFilters, filterInfo.ExceptionFilters);
MergeFiltersIntoList(orderedFilters, filterInfo.ResultFilters);
return filterInfo;
}
public abstract ParameterDescriptor[] GetParameters();
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
Justification = "This method may perform non-trivial work.")]
public virtual ICollection<ActionSelector> GetSelectors() {
return _emptySelectors;
}
public virtual bool IsDefined(Type attributeType, bool inherit) {
if (attributeType == null) {
throw new ArgumentNullException("attributeType");
}
return false;
}
internal static void MergeFiltersIntoList<TFilter>(IList<FilterAttribute> allFilters, IList<TFilter> destFilters) where TFilter : class {
foreach (FilterAttribute filter in allFilters) {
TFilter castFilter = filter as TFilter;
if (castFilter != null) {
destFilters.Add(castFilter);
}
}
}
internal static IEnumerable<FilterAttribute> RemoveOverriddenFilters(IEnumerable<FilterAttribute> filters) {
// If an attribute is declared on both the controller and on an action method and that attribute's
// type has AllowMultiple = false (which is the default for attributes), we should ignore the attributes
// declared on the controller. The CLR's reflection implementation follows a similar algorithm when it
// encounters an overridden virtual method where both the base and the override contain some
// AllowMultiple = false attribute.
// Key = attribute type
// Value = -1 if AllowMultiple true, last index of this attribute type if AllowMultiple false
Dictionary<Type, int> attrsIndexes = new Dictionary<Type, int>();
FilterAttribute[] filtersList = filters.ToArray();
for (int i = 0; i < filtersList.Length; i++) {
FilterAttribute filter = filtersList[i];
Type filterType = filter.GetType();
int lastIndex;
if (attrsIndexes.TryGetValue(filterType, out lastIndex)) {
if (lastIndex >= 0) {
// this filter already exists and AllowMultiple = false, so clear last entry
filtersList[lastIndex] = null;
attrsIndexes[filterType] = i;
}
}
else {
// not found - add to dictionary
// exactly one AttributeUsageAttribute will always be present
bool allowMultiple = _allowMultiplAttributesCache.IsMultiUseAttribute(filterType);
attrsIndexes[filterType] = (allowMultiple) ? -1 : i;
}
}
// any duplicated attributes have now been nulled out, so just return remaining attributes
return filtersList.Where(attr => attr != null);
}
internal static string VerifyActionMethodIsCallable(MethodInfo methodInfo) {
// we can't call instance methods where the 'this' parameter is a type other than ControllerBase
if (!methodInfo.IsStatic && !typeof(ControllerBase).IsAssignableFrom(methodInfo.ReflectedType)) {
return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallInstanceMethodOnNonControllerType,
methodInfo, methodInfo.ReflectedType.FullName);
}
// we can't call methods with open generic type parameters
if (methodInfo.ContainsGenericParameters) {
return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallOpenGenericMethods,
methodInfo, methodInfo.ReflectedType.FullName);
}
// we can't call methods with ref/out parameters
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
foreach (ParameterInfo parameterInfo in parameterInfos) {
if (parameterInfo.IsOut || parameterInfo.ParameterType.IsByRef) {
return String.Format(CultureInfo.CurrentUICulture, MvcResources.ReflectedActionDescriptor_CannotCallMethodsWithOutOrRefParameters,
methodInfo, methodInfo.ReflectedType.FullName, parameterInfo);
}
}
// we can call this method
return null;
}
private sealed class AllowMultipleAttributesCache : ReaderWriterCache<Type, bool> {
public bool IsMultiUseAttribute(Type attributeType) {
return FetchOrCreateItem(attributeType, () => AttributeUsageAllowsMultiple(attributeType));
}
private static bool AttributeUsageAllowsMultiple(Type type) {
return (((AttributeUsageAttribute[])type.GetCustomAttributes(typeof(AttributeUsageAttribute), true))[0]).AllowMultiple;
}
}
}
}
|