#147 - OWIN: Throw KeyNotFoundException if the underlying Feature is missing. Return defaults for required entries.

This commit is contained in:
Chris Ross 2014-11-07 14:08:43 -08:00
parent 76bb27e5d3
commit 1dd3a2ee1f
2 changed files with 118 additions and 28 deletions

View File

@ -35,24 +35,32 @@ namespace Microsoft.AspNet.Owin
public OwinEnvironment(HttpContext context) public OwinEnvironment(HttpContext context)
{ {
if (context.GetFeature<IHttpRequestFeature>() == null)
{
throw new ArgumentException("Missing required IHttpRequestFeature", "context");
}
if (context.GetFeature<IHttpResponseFeature>() == null)
{
throw new ArgumentException("Missing required IHttpResponseFeature", "context");
}
_context = context; _context = context;
_entries = new Dictionary<string, FeatureMap>() _entries = new Dictionary<string, FeatureMap>()
{ {
{ OwinConstants.CallCancelled, new FeatureMap<IHttpRequestLifetimeFeature>(feature => feature.RequestAborted) }, { OwinConstants.RequestProtocol, new FeatureMap<IHttpRequestFeature>(feature => feature.Protocol, () => string.Empty, (feature, value) => feature.Protocol = Convert.ToString(value)) },
{ OwinConstants.RequestProtocol, new FeatureMap<IHttpRequestFeature>(feature => feature.Protocol, (feature, value) => feature.Protocol = Convert.ToString(value)) }, { OwinConstants.RequestScheme, new FeatureMap<IHttpRequestFeature>(feature => feature.Scheme, () => string.Empty, (feature, value) => feature.Scheme = Convert.ToString(value)) },
{ OwinConstants.RequestScheme, new FeatureMap<IHttpRequestFeature>(feature => feature.Scheme, (feature, value) => feature.Scheme = Convert.ToString(value)) }, { OwinConstants.RequestMethod, new FeatureMap<IHttpRequestFeature>(feature => feature.Method, () => string.Empty, (feature, value) => feature.Method = Convert.ToString(value)) },
{ OwinConstants.RequestMethod, new FeatureMap<IHttpRequestFeature>(feature => feature.Method, (feature, value) => feature.Method = Convert.ToString(value)) }, { OwinConstants.RequestPathBase, new FeatureMap<IHttpRequestFeature>(feature => feature.PathBase, () => string.Empty, (feature, value) => feature.PathBase = Convert.ToString(value)) },
{ OwinConstants.RequestPathBase, new FeatureMap<IHttpRequestFeature>(feature => feature.PathBase, (feature, value) => feature.PathBase = Convert.ToString(value)) }, { OwinConstants.RequestPath, new FeatureMap<IHttpRequestFeature>(feature => feature.Path, () => string.Empty, (feature, value) => feature.Path = Convert.ToString(value)) },
{ OwinConstants.RequestPath, new FeatureMap<IHttpRequestFeature>(feature => feature.Path, (feature, value) => feature.Path = Convert.ToString(value)) }, { OwinConstants.RequestQueryString, new FeatureMap<IHttpRequestFeature>(feature => Utilities.RemoveQuestionMark(feature.QueryString), () => string.Empty,
{ OwinConstants.RequestQueryString, new FeatureMap<IHttpRequestFeature>(feature => Utilities.RemoveQuestionMark(feature.QueryString),
(feature, value) => feature.QueryString = Utilities.AddQuestionMark(Convert.ToString(value))) }, (feature, value) => feature.QueryString = Utilities.AddQuestionMark(Convert.ToString(value))) },
{ OwinConstants.RequestHeaders, new FeatureMap<IHttpRequestFeature>(feature => feature.Headers, (feature, value) => feature.Headers = (IDictionary<string, string[]>)value) }, { OwinConstants.RequestHeaders, new FeatureMap<IHttpRequestFeature>(feature => feature.Headers, (feature, value) => feature.Headers = (IDictionary<string, string[]>)value) },
{ OwinConstants.RequestBody, new FeatureMap<IHttpRequestFeature>(feature => feature.Body, (feature, value) => feature.Body = (Stream)value) }, { OwinConstants.RequestBody, new FeatureMap<IHttpRequestFeature>(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) },
{ OwinConstants.ResponseStatusCode, new FeatureMap<IHttpResponseFeature>(feature => feature.StatusCode, (feature, value) => feature.StatusCode = Convert.ToInt32(value)) }, { OwinConstants.ResponseStatusCode, new FeatureMap<IHttpResponseFeature>(feature => feature.StatusCode, () => 200, (feature, value) => feature.StatusCode = Convert.ToInt32(value)) },
{ OwinConstants.ResponseReasonPhrase, new FeatureMap<IHttpResponseFeature>(feature => feature.ReasonPhrase, (feature, value) => feature.ReasonPhrase = Convert.ToString(value)) }, { OwinConstants.ResponseReasonPhrase, new FeatureMap<IHttpResponseFeature>(feature => feature.ReasonPhrase, (feature, value) => feature.ReasonPhrase = Convert.ToString(value)) },
{ OwinConstants.ResponseHeaders, new FeatureMap<IHttpResponseFeature>(feature => feature.Headers, (feature, value) => feature.Headers = (IDictionary<string, string[]>)value) }, { OwinConstants.ResponseHeaders, new FeatureMap<IHttpResponseFeature>(feature => feature.Headers, (feature, value) => feature.Headers = (IDictionary<string, string[]>)value) },
{ OwinConstants.ResponseBody, new FeatureMap<IHttpResponseFeature>(feature => feature.Body, (feature, value) => feature.Body = (Stream)value) }, { OwinConstants.ResponseBody, new FeatureMap<IHttpResponseFeature>(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) },
{ OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap<IHttpResponseFeature>(feature => new Action<Action<object>, object>(feature.OnSendingHeaders)) }, { OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap<IHttpResponseFeature>(feature => new Action<Action<object>, object>(feature.OnSendingHeaders)) },
{ OwinConstants.CommonKeys.LocalPort, new FeatureMap<IHttpConnectionFeature>(feature => feature.LocalPort.ToString(CultureInfo.InvariantCulture), { OwinConstants.CommonKeys.LocalPort, new FeatureMap<IHttpConnectionFeature>(feature => feature.LocalPort.ToString(CultureInfo.InvariantCulture),
@ -70,11 +78,28 @@ namespace Microsoft.AspNet.Owin
{ OwinConstants.SendFiles.SendAsync, new FeatureMap<IHttpSendFileFeature>(feature => new SendFileFunc(feature.SendFileAsync)) }, { OwinConstants.SendFiles.SendAsync, new FeatureMap<IHttpSendFileFeature>(feature => new SendFileFunc(feature.SendFileAsync)) },
{ OwinConstants.Security.User, new FeatureMap<IHttpAuthenticationFeature>(feature => feature.User, { OwinConstants.Security.User, new FeatureMap<IHttpAuthenticationFeature>(feature => feature.User,
(feature, value) => feature.User = Utilities.MakeClaimsPrincipal((IPrincipal)value), ()=> null, (feature, value) => feature.User = Utilities.MakeClaimsPrincipal((IPrincipal)value),
() => new HttpAuthenticationFeature()) () => new HttpAuthenticationFeature())
}, },
}; };
// owin.CallCancelled is required but the feature may not be present.
object ignored;
if (context.GetFeature<IHttpRequestLifetimeFeature>() != null)
{
_entries[OwinConstants.CallCancelled] = new FeatureMap<IHttpRequestLifetimeFeature>(feature => feature.RequestAborted);
}
else if (!_context.Items.TryGetValue(OwinConstants.CallCancelled, out ignored))
{
_context.Items[OwinConstants.CallCancelled] = CancellationToken.None;
}
// owin.Version is required.
if (!context.Items.TryGetValue(OwinConstants.OwinVersion, out ignored))
{
_context.Items[OwinConstants.OwinVersion] = "1.0";
}
if (context.Request.IsSecure) if (context.Request.IsSecure)
{ {
_entries.Add(OwinConstants.CommonKeys.ClientCertificate, new FeatureMap<IHttpClientCertificateFeature>(feature => feature.ClientCertificate, _entries.Add(OwinConstants.CommonKeys.ClientCertificate, new FeatureMap<IHttpClientCertificateFeature>(feature => feature.ClientCertificate,
@ -108,14 +133,17 @@ namespace Microsoft.AspNet.Owin
bool IDictionary<string, object>.ContainsKey(string key) bool IDictionary<string, object>.ContainsKey(string key)
{ {
return _entries.ContainsKey(key) || _context.Items.ContainsKey(key); object value;
return ((IDictionary<string, object>)this).TryGetValue(key, out value);
} }
ICollection<string> IDictionary<string, object>.Keys ICollection<string> IDictionary<string, object>.Keys
{ {
get get
{ {
return _entries.Keys.Concat(_context.Items.Keys.Select(key => Convert.ToString(key))).ToList(); object value;
return _entries.Where(pair => pair.Value.TryGet(_context, out value))
.Select(pair => pair.Key).Concat(_context.Items.Keys.Select(key => Convert.ToString(key))).ToList();
} }
} }
@ -131,9 +159,8 @@ namespace Microsoft.AspNet.Owin
bool IDictionary<string, object>.TryGetValue(string key, out object value) bool IDictionary<string, object>.TryGetValue(string key, out object value)
{ {
FeatureMap entry; FeatureMap entry;
if (_entries.TryGetValue(key, out entry)) if (_entries.TryGetValue(key, out entry) && entry.TryGet(_context, out value))
{ {
value = entry.Get(_context);
return true; return true;
} }
return _context.Items.TryGetValue(key, out value); return _context.Items.TryGetValue(key, out value);
@ -149,11 +176,11 @@ namespace Microsoft.AspNet.Owin
get get
{ {
FeatureMap entry; FeatureMap entry;
if (_entries.TryGetValue(key, out entry))
{
return entry.Get(_context);
}
object value; object value;
if (_entries.TryGetValue(key, out entry) && entry.TryGet(_context, out value))
{
return value;
}
if (_context.Items.TryGetValue(key, out value)) if (_context.Items.TryGetValue(key, out value))
{ {
return value; return value;
@ -232,7 +259,11 @@ namespace Microsoft.AspNet.Owin
{ {
foreach (var entryPair in _entries) foreach (var entryPair in _entries)
{ {
yield return new KeyValuePair<string, object>(entryPair.Key, entryPair.Value.Get(_context)); object value;
if (entryPair.Value.TryGet(_context, out value))
{
yield return new KeyValuePair<string, object>(entryPair.Key, value);
}
} }
foreach (var entryPair in _context.Items) foreach (var entryPair in _context.Items)
{ {
@ -248,26 +279,37 @@ namespace Microsoft.AspNet.Owin
public class FeatureMap public class FeatureMap
{ {
public FeatureMap(Type featureInterface, Func<object, object> getter) public FeatureMap(Type featureInterface, Func<object, object> getter)
: this(featureInterface, getter, setter: null) : this(featureInterface, getter, defaultFactory: null)
{
}
public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory)
: this(featureInterface, getter, defaultFactory, setter: null)
{ {
} }
public FeatureMap(Type featureInterface, Func<object, object> getter, Action<object, object> setter) public FeatureMap(Type featureInterface, Func<object, object> getter, Action<object, object> setter)
: this(featureInterface, getter, setter, featureFactory: null) : this(featureInterface, getter, defaultFactory: null, setter: setter)
{ {
} }
public FeatureMap(Type featureInterface, Func<object, object> getter, Action<object, object> setter, Func<object> featureFactory) public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory, Action<object, object> setter)
: this(featureInterface, getter, defaultFactory, setter, featureFactory: null)
{
}
public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory, Action<object, object> setter, Func<object> featureFactory)
{ {
FeatureInterface = featureInterface; FeatureInterface = featureInterface;
Getter = getter; Getter = getter;
Setter = setter; Setter = setter;
DefaultFactory = defaultFactory;
FeatureFactory = featureFactory; FeatureFactory = featureFactory;
} }
private Type FeatureInterface { get; set; } private Type FeatureInterface { get; set; }
private Func<object, object> Getter { get; set; } private Func<object, object> Getter { get; set; }
private Action<object, object> Setter { get; set; } private Action<object, object> Setter { get; set; }
private Func<object> DefaultFactory { get; set; }
private Func<object> FeatureFactory { get; set; } private Func<object> FeatureFactory { get; set; }
public bool CanSet public bool CanSet
@ -275,14 +317,20 @@ namespace Microsoft.AspNet.Owin
get { return Setter != null; } get { return Setter != null; }
} }
internal object Get(HttpContext context) internal bool TryGet(HttpContext context, out object value)
{ {
object featureInstance = context.GetFeature(FeatureInterface); object featureInstance = context.GetFeature(FeatureInterface);
if (featureInstance == null) if (featureInstance == null)
{ {
return null; value = null;
return false;
} }
return Getter(featureInstance); value = Getter(featureInstance);
if (value == null && DefaultFactory != null)
{
value = DefaultFactory();
}
return true;
} }
internal void Set(HttpContext context, object value) internal void Set(HttpContext context, object value)
@ -311,13 +359,23 @@ namespace Microsoft.AspNet.Owin
{ {
} }
public FeatureMap(Func<TFeature, object> getter, Func<object> defaultFactory)
: base(typeof(TFeature), feature => getter((TFeature)feature), defaultFactory)
{
}
public FeatureMap(Func<TFeature, object> getter, Action<TFeature, object> setter) public FeatureMap(Func<TFeature, object> getter, Action<TFeature, object> setter)
: base(typeof(TFeature), feature => getter((TFeature)feature), (feature, value) => setter((TFeature)feature, value)) : base(typeof(TFeature), feature => getter((TFeature)feature), (feature, value) => setter((TFeature)feature, value))
{ {
} }
public FeatureMap(Func<TFeature, object> getter, Action<TFeature, object> setter, Func<TFeature> featureFactory) public FeatureMap(Func<TFeature, object> getter, Func<object> defaultFactory, Action<TFeature, object> setter)
: base(typeof(TFeature), feature => getter((TFeature)feature), (feature, value) => setter((TFeature)feature, value), () => featureFactory()) : base(typeof(TFeature), feature => getter((TFeature)feature), defaultFactory,(feature, value) => setter((TFeature)feature, value))
{
}
public FeatureMap(Func<TFeature, object> getter, Func<object> defaultFactory, Action<TFeature, object> setter, Func<TFeature> featureFactory)
: base(typeof(TFeature), feature => getter((TFeature)feature), defaultFactory, (feature, value) => setter((TFeature)feature, value), () => featureFactory())
{ {
} }
} }

View File

@ -5,7 +5,9 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.HttpFeature;
using Microsoft.AspNet.PipelineCore; using Microsoft.AspNet.PipelineCore;
using Xunit; using Xunit;
@ -94,6 +96,36 @@ namespace Microsoft.AspNet.Owin
Assert.Equal(201, context.Response.StatusCode); Assert.Equal(201, context.Response.StatusCode);
} }
[Theory]
[InlineData("server.LocalPort")]
public void OwinEnvironmentDoesNotContainEntriesForMissingFeatures(string key)
{
HttpContext context = CreateContext();
IDictionary<string, object> env = new OwinEnvironment(context);
object value;
Assert.False(env.TryGetValue(key, out value));
Assert.Throws<KeyNotFoundException>(() => env[key]);
Assert.False(env.Keys.Contains(key));
Assert.False(env.ContainsKey(key));
}
[Fact]
public void OwinEnvironmentSuppliesDefaultsForMissingRequiredEntries()
{
HttpContext context = CreateContext();
IDictionary<string, object> env = new OwinEnvironment(context);
object value;
Assert.True(env.TryGetValue("owin.CallCancelled", out value), "owin.CallCancelled");
Assert.True(env.TryGetValue("owin.Version", out value), "owin.Version");
Assert.Equal(CancellationToken.None, env["owin.CallCancelled"]);
Assert.Equal("1.0", env["owin.Version"]);
}
private HttpContext CreateContext() private HttpContext CreateContext()
{ {
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();