Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 07f6405

Browse files
committed
Remove non PCL-compatible TypeDescriptor from Reflection API's, use custom Dictionary instead
1 parent 9c6bf68 commit 07f6405

File tree

3 files changed

+267
-19
lines changed

3 files changed

+267
-19
lines changed

src/ServiceStack.Text/ReflectionExtensions.cs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,9 +1022,19 @@ public static PropertyInfo[] AllProperties(this Type type)
10221022
#endif
10231023
}
10241024

1025-
static readonly Dictionary<string, List<Attribute>> propertyAttributesMap
1025+
//Should only register Runtime Attributes on StartUp, So using non-ThreadSafe Dictionary is OK
1026+
static Dictionary<string, List<Attribute>> propertyAttributesMap
10261027
= new Dictionary<string, List<Attribute>>();
10271028

1029+
static Dictionary<Type, List<Attribute>> typeAttributesMap
1030+
= new Dictionary<Type, List<Attribute>>();
1031+
1032+
public static void ClearRuntimeAttributes()
1033+
{
1034+
propertyAttributesMap = new Dictionary<string, List<Attribute>>();
1035+
typeAttributesMap = new Dictionary<Type, List<Attribute>>();
1036+
}
1037+
10281038
internal static string UniqueKey(this PropertyInfo pi)
10291039
{
10301040
if (pi.DeclaringType == null)
@@ -1035,12 +1045,15 @@ internal static string UniqueKey(this PropertyInfo pi)
10351045

10361046
public static Type AddAttributes(this Type type, params Attribute[] attrs)
10371047
{
1038-
#if NETFX_CORE || SL5 || PCL
1039-
throw new NotSupportedException("Adding Attributes at runtime is not supported on this platform");
1040-
#else
1041-
TypeDescriptor.AddAttributes(type, attrs);
1048+
List<Attribute> typeAttrs;
1049+
if (!typeAttributesMap.TryGetValue(type, out typeAttrs))
1050+
{
1051+
typeAttributesMap[type] = typeAttrs = new List<Attribute>();
1052+
}
1053+
1054+
typeAttrs.AddRange(attrs);
1055+
10421056
return type;
1043-
#endif
10441057
}
10451058

10461059
/// <summary>
@@ -1194,21 +1207,17 @@ public static object[] AllAttributes(this Type type)
11941207
{
11951208
#if (NETFX_CORE || PCL)
11961209
return type.GetTypeInfo().GetCustomAttributes(true).ToArray();
1197-
#elif SL5
1198-
return type.GetCustomAttributes(true);
11991210
#else
1200-
return TypeDescriptor.GetAttributes(type).Cast<object>().ToArray();
1211+
return type.GetCustomAttributes(true).Union(type.GetRuntimeAttributes()).ToArray();
12011212
#endif
12021213
}
12031214

12041215
public static object[] AllAttributes(this Type type, Type attrType)
12051216
{
12061217
#if (NETFX_CORE || PCL)
12071218
return type.GetTypeInfo().GetCustomAttributes(true).Where(x => x.GetType() == attrType).ToArray();
1208-
#elif SL5
1209-
return type.GetCustomAttributes(attrType, true);
12101219
#else
1211-
return TypeDescriptor.GetAttributes(type).OfType<Attribute>().ToArray();
1220+
return type.GetCustomAttributes(true).Union(type.GetRuntimeAttributes()).ToArray();
12121221
#endif
12131222
}
12141223

@@ -1241,17 +1250,34 @@ public static TAttr[] AllAttributes<TAttr>(this PropertyInfo pi)
12411250
return pi.AllAttributes(typeof(TAttr)).Cast<TAttr>().ToArray();
12421251
}
12431252

1253+
static IEnumerable<T> GetRuntimeAttributes<T>(this Type type)
1254+
{
1255+
List<Attribute> attrs;
1256+
return typeAttributesMap.TryGetValue(type, out attrs)
1257+
? attrs.OfType<T>()
1258+
: new List<T>();
1259+
}
1260+
1261+
static IEnumerable<Attribute> GetRuntimeAttributes(this Type type, Type attrType = null)
1262+
{
1263+
List<Attribute> attrs;
1264+
return typeAttributesMap.TryGetValue(type, out attrs)
1265+
? attrs.Where(x => attrType == null || x.GetType() == attrType)
1266+
: new List<Attribute>();
1267+
}
1268+
12441269
public static TAttr[] AllAttributes<TAttr>(this Type type)
12451270
#if (NETFX_CORE || PCL)
12461271
where TAttr : Attribute
12471272
#endif
12481273
{
12491274
#if (NETFX_CORE || PCL)
12501275
return type.GetTypeInfo().GetCustomAttributes<TAttr>(true).ToArray();
1251-
#elif SL5
1252-
return type.GetCustomAttributes(typeof(TAttr), true).Cast<TAttr>().ToArray();
12531276
#else
1254-
return TypeDescriptor.GetAttributes(type).OfType<TAttr>().ToArray();
1277+
return type.GetCustomAttributes(typeof(TAttr), true)
1278+
.OfType<TAttr>()
1279+
.Union(type.GetRuntimeAttributes<TAttr>())
1280+
.ToArray();
12551281
#endif
12561282
}
12571283

@@ -1262,11 +1288,10 @@ public static TAttr FirstAttribute<TAttr>(this Type type) where TAttr : class
12621288
return (TAttr)type.GetTypeInfo().GetCustomAttributes(typeof(TAttr), true)
12631289
.Cast<TAttr>()
12641290
.FirstOrDefault();
1265-
#elif SL5
1266-
return (TAttr)type.GetCustomAttributes(typeof(TAttr), true)
1267-
.FirstOrDefault();
12681291
#else
1269-
return TypeDescriptor.GetAttributes(type).OfType<TAttr>().FirstOrDefault();
1292+
return (TAttr)type.GetCustomAttributes(typeof(TAttr), true)
1293+
.FirstOrDefault()
1294+
?? type.GetRuntimeAttributes<TAttr>().FirstOrDefault();
12701295
#endif
12711296
}
12721297

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
using System;
2+
using System.Linq;
3+
using NUnit.Framework;
4+
5+
namespace ServiceStack.Text.Tests
6+
{
7+
[TestFixture]
8+
public class AttributeTests
9+
{
10+
[Test]
11+
public void Does_get_Single_Default_Attribute()
12+
{
13+
var attrs = typeof(DefaultWithSingleAttribute).AllAttributes<RouteDefaultAttribute>();
14+
Assert.That(attrs[0].ToString(), Is.EqualTo("/path:"));
15+
16+
var attr = typeof(DefaultWithSingleAttribute).FirstAttribute<RouteDefaultAttribute>();
17+
Assert.That(attr.ToString(), Is.EqualTo("/path:"));
18+
}
19+
20+
[Test]
21+
public void Does_get_Single_TypeId_Attribute()
22+
{
23+
var attrs = typeof(TypeIdWithSingleAttribute).AllAttributes<RouteTypeIdAttribute>();
24+
Assert.That(attrs[0].ToString(), Is.EqualTo("/path:"));
25+
26+
var attr = typeof(TypeIdWithSingleAttribute).FirstAttribute<RouteTypeIdAttribute>();
27+
Assert.That(attr.ToString(), Is.EqualTo("/path:"));
28+
}
29+
30+
[Test]
31+
public void Does_get_Multiple_Default_Attributes()
32+
{
33+
var attrs = typeof(DefaultWithMultipleAttributes).AllAttributes<RouteDefaultAttribute>();
34+
Assert.That(attrs.Length, Is.EqualTo(4));
35+
36+
var values = attrs.ToList().ConvertAll(x => x.ToString());
37+
38+
Assert.That(values, Is.EquivalentTo(new[] {
39+
"/path:", "/path/2:", "/path:GET", "/path:POST",
40+
}));
41+
42+
var objAttrs = typeof(DefaultWithMultipleAttributes).AllAttributes();
43+
values = objAttrs.ToList().ConvertAll(x => x.ToString());
44+
45+
Assert.That(values, Is.EquivalentTo(new[] {
46+
"/path:", "/path/2:", "/path:GET", "/path:POST",
47+
}));
48+
49+
objAttrs = typeof(DefaultWithMultipleAttributes).AllAttributes(typeof(RouteDefaultAttribute));
50+
values = objAttrs.ToList().ConvertAll(x => x.ToString());
51+
52+
Assert.That(values, Is.EquivalentTo(new[] {
53+
"/path:", "/path/2:", "/path:GET", "/path:POST",
54+
}));
55+
}
56+
57+
[Test]
58+
public void Does_get_Multiple_TypeId_Attributes()
59+
{
60+
var attrs = typeof(TypeIdWithMultipleAttributes).AllAttributes<RouteTypeIdAttribute>();
61+
Assert.That(attrs.Length, Is.EqualTo(4));
62+
63+
var values = attrs.ToList().ConvertAll(x => x.ToString());
64+
65+
Assert.That(values, Is.EquivalentTo(new[] {
66+
"/path:", "/path/2:", "/path:GET", "/path:POST",
67+
}));
68+
69+
var objAttrs = typeof(TypeIdWithMultipleAttributes).AllAttributes();
70+
values = objAttrs.ToList().ConvertAll(x => x.ToString());
71+
72+
Assert.That(values, Is.EquivalentTo(new[] {
73+
"/path:", "/path/2:", "/path:GET", "/path:POST",
74+
}));
75+
76+
objAttrs = typeof(TypeIdWithMultipleAttributes).AllAttributes(typeof(RouteTypeIdAttribute));
77+
values = objAttrs.ToList().ConvertAll(x => x.ToString());
78+
79+
Assert.That(values, Is.EquivalentTo(new[] {
80+
"/path:", "/path/2:", "/path:GET", "/path:POST",
81+
}));
82+
}
83+
}
84+
85+
[TestFixture]
86+
public class RuntimeAttributesTests
87+
{
88+
[Test]
89+
public void Can_add_to_Multiple_Default_Attributes()
90+
{
91+
typeof (DefaultWithMultipleAttributes).AddAttributes(
92+
new RouteDefaultAttribute("/path-add"),
93+
new RouteDefaultAttribute("/path-add", "GET"));
94+
95+
var attrs = typeof(DefaultWithMultipleAttributes).AllAttributes<RouteDefaultAttribute>();
96+
Assert.That(attrs.Length, Is.EqualTo(6));
97+
98+
var values = attrs.ToList().ConvertAll(x => x.ToString());
99+
100+
Assert.That(values, Is.EquivalentTo(new[] {
101+
"/path:", "/path/2:", "/path:GET", "/path:POST",
102+
"/path-add:", "/path-add:GET",
103+
}));
104+
105+
var objAttrs = typeof(DefaultWithMultipleAttributes).AllAttributes();
106+
values = objAttrs.ToList().ConvertAll(x => x.ToString());
107+
108+
Assert.That(values, Is.EquivalentTo(new[] {
109+
"/path:", "/path/2:", "/path:GET", "/path:POST",
110+
"/path-add:", "/path-add:GET",
111+
}));
112+
113+
objAttrs = typeof(DefaultWithMultipleAttributes).AllAttributes(typeof(RouteDefaultAttribute));
114+
values = objAttrs.ToList().ConvertAll(x => x.ToString());
115+
116+
Assert.That(values, Is.EquivalentTo(new[] {
117+
"/path:", "/path/2:", "/path:GET", "/path:POST",
118+
"/path-add:", "/path-add:GET",
119+
}));
120+
}
121+
122+
[Test]
123+
public void Does_get_Multiple_TypeId_Attributes()
124+
{
125+
typeof(TypeIdWithMultipleAttributes).AddAttributes(
126+
new RouteTypeIdAttribute("/path-add"),
127+
new RouteTypeIdAttribute("/path-add", "GET"));
128+
129+
var attrs = typeof(TypeIdWithMultipleAttributes).AllAttributes<RouteTypeIdAttribute>();
130+
Assert.That(attrs.Length, Is.EqualTo(6));
131+
132+
var values = attrs.ToList().ConvertAll(x => x.ToString());
133+
134+
Assert.That(values, Is.EquivalentTo(new[] {
135+
"/path:", "/path/2:", "/path:GET", "/path:POST",
136+
"/path-add:", "/path-add:GET",
137+
}));
138+
139+
var objAttrs = typeof(TypeIdWithMultipleAttributes).AllAttributes();
140+
values = objAttrs.ToList().ConvertAll(x => x.ToString());
141+
142+
Assert.That(values, Is.EquivalentTo(new[] {
143+
"/path:", "/path/2:", "/path:GET", "/path:POST",
144+
"/path-add:", "/path-add:GET",
145+
}));
146+
147+
objAttrs = typeof(TypeIdWithMultipleAttributes).AllAttributes(typeof(RouteTypeIdAttribute));
148+
values = objAttrs.ToList().ConvertAll(x => x.ToString());
149+
150+
Assert.That(values, Is.EquivalentTo(new[] {
151+
"/path:", "/path/2:", "/path:GET", "/path:POST",
152+
"/path-add:", "/path-add:GET",
153+
}));
154+
}
155+
}
156+
157+
[RouteTypeId("/path")]
158+
public class TypeIdWithSingleAttribute { }
159+
160+
[RouteTypeId("/path")]
161+
[RouteTypeId("/path/2")]
162+
[RouteTypeId("/path", "GET")]
163+
[RouteTypeId("/path", "POST")]
164+
public class TypeIdWithMultipleAttributes { }
165+
166+
[RouteDefault("/path")]
167+
public class DefaultWithSingleAttribute { }
168+
169+
[RouteDefault("/path")]
170+
[RouteDefault("/path/2")]
171+
[RouteDefault("/path", "GET")]
172+
[RouteDefault("/path", "POST")]
173+
public class DefaultWithMultipleAttributes { }
174+
175+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
176+
public class RouteTypeIdAttribute : Attribute
177+
{
178+
public RouteTypeIdAttribute(string path) : this(path, null) {}
179+
public RouteTypeIdAttribute(string path, string verbs)
180+
{
181+
Path = path;
182+
Verbs = verbs;
183+
}
184+
185+
public string Path { get; set; }
186+
public string Verbs { get; set; }
187+
188+
public override object TypeId
189+
{
190+
get
191+
{
192+
return (Path ?? "")
193+
+ (Verbs ?? "");
194+
}
195+
}
196+
197+
public override string ToString()
198+
{
199+
return "{0}:{1}".Fmt(Path, Verbs);
200+
}
201+
}
202+
203+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
204+
public class RouteDefaultAttribute : Attribute
205+
{
206+
public RouteDefaultAttribute(string path) : this(path, null) {}
207+
public RouteDefaultAttribute(string path, string verbs)
208+
{
209+
Path = path;
210+
Verbs = verbs;
211+
}
212+
213+
public string Path { get; set; }
214+
public string Verbs { get; set; }
215+
216+
public override string ToString()
217+
{
218+
return "{0}:{1}".Fmt(Path, Verbs);
219+
}
220+
}
221+
222+
}

tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
</Reference>
177177
</ItemGroup>
178178
<ItemGroup>
179+
<Compile Include="AttributeTests.cs" />
179180
<Compile Include="SerializationDelegatePerformanceTests.cs" />
180181
<Compile Include="SerializationHookTests.cs" />
181182
<Compile Include="StaticAccessorTests.cs" />

0 commit comments

Comments
 (0)