#339 Reduce IFeatureCollection surface area.

This commit is contained in:
Chris R 2015-07-28 12:24:42 -07:00
parent 5d7ec0e2c6
commit ac945a0bcf
10 changed files with 174 additions and 349 deletions

View File

@ -4,15 +4,16 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Http.Features
{
public class FeatureCollection : IFeatureCollection
{
private static KeyComparer FeatureKeyComparer = new FeatureCollection.KeyComparer();
private readonly IFeatureCollection _defaults;
private readonly IDictionary<Type, object> _featureByFeatureType = new Dictionary<Type, object>();
private readonly object _containerSync = new object();
private IDictionary<Type, object> _features;
private volatile int _containerRevision;
public FeatureCollection()
@ -24,53 +25,38 @@ namespace Microsoft.AspNet.Http.Features
_defaults = defaults;
}
public object GetInterface()
{
return GetInterface(null);
}
public object GetInterface([NotNull] Type type)
{
object feature;
if (_featureByFeatureType.TryGetValue(type, out feature))
{
return feature;
}
if (_defaults != null && _defaults.TryGetValue(type, out feature))
{
return feature;
}
return null;
}
void SetInterface([NotNull] Type type, object feature)
{
if (feature == null)
{
Remove(type);
return;
}
lock (_containerSync)
{
_featureByFeatureType[type] = feature;
_containerRevision++;
}
}
public virtual int Revision
{
get { return _containerRevision; }
get { return _containerRevision + (_defaults?.Revision ?? 0); }
}
public void Dispose()
{
}
public bool IsReadOnly { get { return false; } }
public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
public object this[[NotNull] Type key]
{
throw new NotImplementedException();
get
{
object result;
return _features != null && _features.TryGetValue(key, out result) ? result : _defaults?[key];
}
set
{
if (value == null)
{
if (_features != null && _features.Remove(key))
{
_containerRevision++;
}
return;
}
if (_features == null)
{
_features = new Dictionary<Type, object>();
}
_features[key] = value;
_containerRevision++;
}
}
IEnumerator IEnumerable.GetEnumerator()
@ -78,89 +64,42 @@ namespace Microsoft.AspNet.Http.Features
return GetEnumerator();
}
public void Add(KeyValuePair<Type, object> item)
public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
{
SetInterface(item.Key, item.Value);
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(KeyValuePair<Type, object> item)
{
object value;
return TryGetValue(item.Key, out value) && Equals(item.Value, value);
}
public void CopyTo(KeyValuePair<Type, object>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public bool Remove(KeyValuePair<Type, object> item)
{
return Contains(item) && Remove(item.Key);
}
public int Count
{
get { throw new NotImplementedException(); }
}
public bool IsReadOnly
{
get { return false; }
}
public bool ContainsKey([NotNull] Type key)
{
return GetInterface(key) != null;
}
public void Add([NotNull] Type key, [NotNull] object value)
{
if (ContainsKey(key))
if (_features != null)
{
throw new ArgumentException();
}
SetInterface(key, value);
}
public bool Remove([NotNull] Type key)
{
lock (_containerSync)
{
if (_featureByFeatureType.Remove(key))
foreach (var pair in _features)
{
_containerRevision++;
return true;
yield return pair;
}
}
if (_defaults != null)
{
// Don't return features masked by the wrapper.
foreach (var pair in _features == null ? _defaults : _defaults.Except(_features, FeatureKeyComparer))
{
yield return pair;
}
return false;
}
}
public bool TryGetValue([NotNull] Type key, out object value)
public virtual void Dispose()
{
value = GetInterface(key);
return value != null;
_defaults?.Dispose();
}
public object this[Type key]
private class KeyComparer : IEqualityComparer<KeyValuePair<Type, object>>
{
get { return GetInterface(key); }
set { SetInterface(key, value); }
}
public bool Equals(KeyValuePair<Type, object> x, KeyValuePair<Type, object> y)
{
return x.Key.Equals(y.Key);
}
public ICollection<Type> Keys
{
get { throw new NotImplementedException(); }
}
public ICollection<object> Values
{
get { throw new NotImplementedException(); }
public int GetHashCode(KeyValuePair<Type, object> obj)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -18,23 +18,19 @@ namespace Microsoft.AspNet.Http.Features
public T Fetch(IFeatureCollection features)
{
if (_revision == features.Revision) return _feature;
object value;
if (features.TryGetValue(typeof(T), out value))
if (_revision == features.Revision)
{
_feature = (T)value;
}
else
{
_feature = default(T);
return _feature;
}
_feature = (T)features[typeof(T)];
_revision = features.Revision;
return _feature;
}
public T Update(IFeatureCollection features, T feature)
{
features[typeof(T)] = _feature = feature;
features[typeof(T)] = feature;
_feature = feature;
_revision = features.Revision;
return feature;
}

View File

@ -3,11 +3,27 @@
using System;
using System.Collections.Generic;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Http.Features
{
public interface IFeatureCollection : IDictionary<Type, object>, IDisposable
public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>>, IDisposable
{
/// <summary>
/// Indicates if the collection can be modified.
/// </summary>
bool IsReadOnly { get; }
/// <summary>
/// Incremented for each modification and can be used verify cached results.
/// </summary>
int Revision { get; }
/// <summary>
/// Gets or sets a given feature. Setting a null value removes the feature.
/// </summary>
/// <param name="key"></param>
/// <returns>The requested feature, or null if it is not present.</returns>
object this[[NotNull] Type key] { get; set; }
}
}

View File

@ -13,6 +13,7 @@
"dnxcore50": {
"dependencies": {
"System.Collections": "4.0.10-beta-*",
"System.Linq": "4.0.0-beta-*",
"System.Net.Primitives": "4.0.10-beta-*",
"System.Net.WebSockets": "4.0.0-beta-*",
"System.Runtime.Extensions": "4.0.10-beta-*",

View File

@ -172,8 +172,7 @@ namespace Microsoft.AspNet.Http.Internal
public override object GetFeature(Type type)
{
object value;
return _features.TryGetValue(type, out value) ? value : null;
return _features[type];
}
public override void SetFeature(Type type, object instance)

View File

@ -2,10 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Reflection;
@ -16,7 +16,6 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Owin
{
@ -111,6 +110,12 @@ namespace Microsoft.AspNet.Owin
set { Prop(OwinConstants.RequestHeaders, value); }
}
string IHttpRequestIdentifierFeature.TraceIdentifier
{
get { return Prop<string>(OwinConstants.RequestId); }
set { Prop(OwinConstants.RequestId, value); }
}
Stream IHttpRequestFeature.Body
{
get { return Prop<Stream>(OwinConstants.RequestBody); }
@ -266,7 +271,7 @@ namespace Microsoft.AspNet.Owin
/// <summary>
/// Gets or sets if the underlying server supports WebSockets. This is enabled by default.
/// The value should be consistant across requests.
/// The value should be consistent across requests.
/// </summary>
public bool SupportsWebSockets { get; set; }
@ -290,17 +295,25 @@ namespace Microsoft.AspNet.Owin
return accept(context);
}
// IFeatureCollection
public int Revision
{
get { return 0; } // Not modifiable
}
public void Add(Type key, object value)
public bool IsReadOnly
{
throw new NotSupportedException();
get { return true; }
}
public bool ContainsKey(Type key)
public object this[Type key]
{
get { return Get(key); }
set { throw new NotSupportedException(); }
}
private bool SupportsInterface(Type key)
{
// Does this type implement the requested interface?
if (key.GetTypeInfo().IsAssignableFrom(GetType().GetTypeInfo()))
@ -325,136 +338,48 @@ namespace Microsoft.AspNet.Owin
return false;
}
public ICollection<Type> Keys
public object Get(Type key)
{
get
if (SupportsInterface(key))
{
var keys = new List<Type>()
{
typeof(IHttpRequestFeature),
typeof(IHttpResponseFeature),
typeof(IHttpConnectionFeature),
typeof(IOwinEnvironmentFeature),
typeof(IHttpRequestLifetimeFeature),
typeof(IHttpAuthenticationFeature),
};
if (SupportsSendFile)
{
keys.Add(typeof(IHttpSendFileFeature));
}
if (SupportsClientCerts)
{
keys.Add(typeof(ITlsConnectionFeature));
}
if (SupportsWebSockets)
{
keys.Add(typeof(IHttpWebSocketFeature));
}
return keys;
return this;
}
return null;
}
public bool Remove(Type key)
public void Set(Type key, object value)
{
throw new NotSupportedException();
}
public bool TryGetValue(Type key, out object value)
IEnumerator IEnumerable.GetEnumerator()
{
if (ContainsKey(key))
{
value = this;
return true;
}
value = null;
return false;
}
public ICollection<object> Values
{
get { throw new NotSupportedException(); }
}
public object this[Type key]
{
get
{
object value;
if (TryGetValue(key, out value))
{
return value;
}
throw new KeyNotFoundException(key.FullName);
}
set
{
throw new NotSupportedException();
}
}
public void Add(KeyValuePair<Type, object> item)
{
throw new NotSupportedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public bool Contains(KeyValuePair<Type, object> item)
{
object result;
return TryGetValue(item.Key, out result) && result.Equals(item.Value);
}
public void CopyTo([NotNull] KeyValuePair<Type, object>[] array, int arrayIndex)
{
if (arrayIndex < 0 || arrayIndex > array.Length)
{
throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, string.Empty);
}
var keys = Keys;
if (keys.Count > array.Length - arrayIndex)
{
throw new ArgumentException();
}
foreach (var key in keys)
{
array[arrayIndex++] = new KeyValuePair<Type, object>(key, this[key]);
}
}
public int Count
{
get { return Keys.Count; }
}
public bool IsReadOnly
{
get { return true; }
}
string IHttpRequestIdentifierFeature.TraceIdentifier
{
get { return Prop<string>(OwinConstants.RequestId); }
set { Prop(OwinConstants.RequestId, value); }
}
public bool Remove(KeyValuePair<Type, object> item)
{
throw new NotSupportedException();
return GetEnumerator();
}
public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
{
return Keys.Select(type => new KeyValuePair<Type, object>(type, this[type])).GetEnumerator();
}
yield return new KeyValuePair<Type, object>(typeof(IHttpRequestFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IHttpResponseFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IHttpConnectionFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IHttpRequestIdentifierFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IHttpRequestLifetimeFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IHttpAuthenticationFeature), this);
yield return new KeyValuePair<Type, object>(typeof(IOwinEnvironmentFeature), this);
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
// Check for conditional features
if (SupportsSendFile)
{
yield return new KeyValuePair<Type, object>(typeof(IHttpSendFileFeature), this);
}
if (SupportsClientCerts)
{
yield return new KeyValuePair<Type, object>(typeof(ITlsConnectionFeature), this);
}
if (SupportsWebSockets)
{
yield return new KeyValuePair<Type, object>(typeof(IHttpWebSocketFeature), this);
}
}
public void Dispose()

View File

@ -0,0 +1,48 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNet.Http.Features
{
public class FeatureCollectionTests
{
[Fact]
public void AddedInterfaceIsReturned()
{
var interfaces = new FeatureCollection();
var thing = new Thing();
interfaces[typeof(IThing)] = thing;
object thing2 = interfaces[typeof(IThing)];
Assert.Equal(thing2, thing);
}
[Fact]
public void IndexerAlsoAddsItems()
{
var interfaces = new FeatureCollection();
var thing = new Thing();
interfaces[typeof(IThing)] = thing;
Assert.Equal(interfaces[typeof(IThing)], thing);
}
[Fact]
public void SetNullValueRemoves()
{
var interfaces = new FeatureCollection();
var thing = new Thing();
interfaces[typeof(IThing)] = thing;
Assert.Equal(interfaces[typeof(IThing)], thing);
interfaces[typeof(IThing)] = null;
object thing2 = interfaces[typeof(IThing)];
Assert.Null(thing2);
}
}
}

View File

@ -1,83 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNet.Http.Features
{
public class InterfaceDictionaryTests
{
[Fact]
public void AddedInterfaceIsReturned()
{
var interfaces = new FeatureCollection();
var thing = new Thing();
interfaces.Add(typeof(IThing), thing);
Assert.Equal(interfaces[typeof(IThing)], thing);
object thing2;
Assert.True(interfaces.TryGetValue(typeof(IThing), out thing2));
Assert.Equal(thing2, thing);
}
[Fact]
public void IndexerAlsoAddsItems()
{
var interfaces = new FeatureCollection();
var thing = new Thing();
interfaces[typeof(IThing)] = thing;
Assert.Equal(interfaces[typeof(IThing)], thing);
object thing2;
Assert.True(interfaces.TryGetValue(typeof(IThing), out thing2));
Assert.Equal(thing2, thing);
}
[Fact]
public void SecondCallToAddThrowsException()
{
var interfaces = new FeatureCollection();
var thing = new Thing();
interfaces.Add(typeof(IThing), thing);
Assert.Throws<ArgumentException>(() => interfaces.Add(typeof(IThing), thing));
}
[Fact]
public void RemovedInterfaceIsRemoved()
{
var interfaces = new FeatureCollection();
var thing = new Thing();
interfaces.Add(typeof(IThing), thing);
Assert.Equal(interfaces[typeof(IThing)], thing);
Assert.True(interfaces.Remove(typeof(IThing)));
object thing2;
Assert.False(interfaces.TryGetValue(typeof(IThing), out thing2));
}
[Fact]
public void SetNullValueRemoves()
{
var interfaces = new FeatureCollection();
var thing = new Thing();
interfaces.Add(typeof(IThing), thing);
Assert.Equal(interfaces[typeof(IThing)], thing);
interfaces[typeof(IThing)] = null;
object thing2;
Assert.False(interfaces.TryGetValue(typeof(IThing), out thing2));
}
}
}

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Http.Features.Internal
var features = new FeatureCollection();
var request = new HttpRequestFeature();
request.QueryString = "foo=bar";
features.Add(typeof(IHttpRequestFeature), request);
features[typeof(IHttpRequestFeature)] = request;
var provider = new QueryFeature(features);

View File

@ -12,9 +12,9 @@ namespace Microsoft.AspNet.Owin
{
private T Get<T>(IFeatureCollection features)
{
object value;
return features.TryGetValue(typeof(T), out value) ? (T)value : default(T);
return (T)features[typeof(T)];
}
private T Get<T>(IDictionary<string, object> env, string key)
{
object value;
@ -63,22 +63,6 @@ namespace Microsoft.AspNet.Owin
Assert.Equal("/pathBase2", Get<string>(env, "owin.RequestPathBase"));
Assert.Equal("name=value2", Get<string>(env, "owin.RequestQueryString"));
}
[Fact]
public void ImplementedInterfacesAreEnumerated()
{
var env = new Dictionary<string, object>
{
{"owin.RequestMethod", "POST"}
};
var features = new OwinFeatureCollection(env);
var entries = features.ToArray();
var keys = features.Keys.ToArray();
Assert.Contains(typeof(IHttpRequestFeature), keys);
Assert.Contains(typeof(IHttpResponseFeature), keys);
}
}
}