diff --git a/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs b/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs index 1b853075da..c7a63c8356 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Security.Claims; using System.Threading; using Microsoft.AspNet.Http.Authentication; +using Microsoft.AspNet.Http.Features; namespace Microsoft.AspNet.Http { @@ -31,7 +32,7 @@ namespace Microsoft.AspNet.Http public abstract CancellationToken RequestAborted { get; set; } - public abstract ISessionCollection Session { get; } + public abstract ISession Session { get; set; } public abstract void Abort(); diff --git a/src/Microsoft.AspNet.Http.Abstractions/ISessionCollection.cs b/src/Microsoft.AspNet.Http.Abstractions/ISessionCollection.cs deleted file mode 100644 index a446704d33..0000000000 --- a/src/Microsoft.AspNet.Http.Abstractions/ISessionCollection.cs +++ /dev/null @@ -1,21 +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 System.Collections.Generic; - -namespace Microsoft.AspNet.Http -{ - public interface ISessionCollection : IEnumerable> - { - byte[] this[string key] { get; set; } - - bool TryGetValue(string key, out byte[] value); - - void Set(string key, ArraySegment value); - - void Remove(string key); - - void Clear(); - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http.Abstractions/project.json b/src/Microsoft.AspNet.Http.Abstractions/project.json index 75763424c1..1f9e83a30c 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/project.json +++ b/src/Microsoft.AspNet.Http.Abstractions/project.json @@ -2,6 +2,7 @@ "version": "1.0.0-*", "description": "ASP.NET 5 HTTP object model. HttpContext and family.", "dependencies": { + "Microsoft.AspNet.Http.Features": "1.0.0-*", "Microsoft.Framework.ActivatorUtilities.Sources": { "type": "build", "version": "1.0.0-*" }, "Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" }, "Microsoft.Framework.WebEncoders.Core": "1.0.0-*" diff --git a/src/Microsoft.AspNet.Http.Extensions/SessionCollectionExtensions.cs b/src/Microsoft.AspNet.Http.Extensions/SessionExtensions.cs similarity index 63% rename from src/Microsoft.AspNet.Http.Extensions/SessionCollectionExtensions.cs rename to src/Microsoft.AspNet.Http.Extensions/SessionExtensions.cs index fbdbd02e40..b2908aed80 100644 --- a/src/Microsoft.AspNet.Http.Extensions/SessionCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Http.Extensions/SessionExtensions.cs @@ -1,14 +1,14 @@ // 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 System.Text; +using Microsoft.AspNet.Http.Features; namespace Microsoft.AspNet.Http { - public static class SessionCollectionExtensions + public static class SessionExtensions { - public static void SetInt32(this ISessionCollection session, string key, int value) + public static void SetInt32(this ISession session, string key, int value) { var bytes = new byte[] { @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Http session.Set(key, bytes); } - public static int? GetInt32(this ISessionCollection session, string key) + public static int? GetInt32(this ISession session, string key) { var data = session.Get(key); if (data == null || data.Length < 4) @@ -30,12 +30,12 @@ namespace Microsoft.AspNet.Http return data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; } - public static void SetString(this ISessionCollection session, string key, string value) + public static void SetString(this ISession session, string key, string value) { session.Set(key, Encoding.UTF8.GetBytes(value)); } - public static string GetString(this ISessionCollection session, string key) + public static string GetString(this ISession session, string key) { var data = session.Get(key); if (data == null) @@ -45,16 +45,11 @@ namespace Microsoft.AspNet.Http return Encoding.UTF8.GetString(data); } - public static byte[] Get(this ISessionCollection session, string key) + public static byte[] Get(this ISession session, string key) { byte[] value = null; session.TryGetValue(key, out value); return value; } - - public static void Set(this ISessionCollection session, string key, byte[] value) - { - session.Set(key, new ArraySegment(value)); - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http.Extensions/project.json b/src/Microsoft.AspNet.Http.Extensions/project.json index aca989fb05..6f5855192b 100644 --- a/src/Microsoft.AspNet.Http.Extensions/project.json +++ b/src/Microsoft.AspNet.Http.Extensions/project.json @@ -3,7 +3,6 @@ "description": "ASP.NET 5 common extension methods for HTTP abstractions and IApplicationBuilder.", "dependencies": { "Microsoft.AspNet.Http.Abstractions": "1.0.0-*", - "Microsoft.AspNet.Http.Features": "1.0.0-*", "Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" }, "Microsoft.Framework.WebEncoders.Core": "1.0.0-*", "Microsoft.Net.Http.Headers": "1.0.0-*" diff --git a/src/Microsoft.AspNet.Http.Features/ISession.cs b/src/Microsoft.AspNet.Http.Features/ISession.cs index ba80fc7324..058853ffc9 100644 --- a/src/Microsoft.AspNet.Http.Features/ISession.cs +++ b/src/Microsoft.AspNet.Http.Features/ISession.cs @@ -1,20 +1,20 @@ // 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 System.Collections.Generic; +using System.Threading.Tasks; namespace Microsoft.AspNet.Http.Features { public interface ISession { - void Load(); + Task LoadAsync(); - void Commit(); + Task CommitAsync(); bool TryGetValue(string key, out byte[] value); - void Set(string key, ArraySegment value); + void Set(string key, byte[] value); void Remove(string key); diff --git a/src/Microsoft.AspNet.Http.Features/ISessionFactory.cs b/src/Microsoft.AspNet.Http.Features/ISessionFactory.cs deleted file mode 100644 index 441f96eeb5..0000000000 --- a/src/Microsoft.AspNet.Http.Features/ISessionFactory.cs +++ /dev/null @@ -1,12 +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. - -namespace Microsoft.AspNet.Http.Features -{ - public interface ISessionFactory - { - bool IsAvailable { get; } - - ISession Create(); - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http.Features/ISessionFeature.cs b/src/Microsoft.AspNet.Http.Features/ISessionFeature.cs index bd3a66883e..4406b8809f 100644 --- a/src/Microsoft.AspNet.Http.Features/ISessionFeature.cs +++ b/src/Microsoft.AspNet.Http.Features/ISessionFeature.cs @@ -3,11 +3,8 @@ namespace Microsoft.AspNet.Http.Features { - // TODO: Is there any reason not to flatten the Factory down into the Feature? public interface ISessionFeature { - ISessionFactory Factory { get; set; } - ISession Session { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http.Features/WebSocketAcceptContext.cs b/src/Microsoft.AspNet.Http.Features/WebSocketAcceptContext.cs index df11f93766..1bc7c2e470 100644 --- a/src/Microsoft.AspNet.Http.Features/WebSocketAcceptContext.cs +++ b/src/Microsoft.AspNet.Http.Features/WebSocketAcceptContext.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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. namespace Microsoft.AspNet.Http.Features diff --git a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs index 6090eed1b9..5dc1c96cfe 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs @@ -124,24 +124,27 @@ namespace Microsoft.AspNet.Http.Internal set { LifetimeFeature.RequestAborted = value; } } - public override ISessionCollection Session + public override ISession Session { get { var feature = SessionFeature; if (feature == null) { - throw new InvalidOperationException("Session has not been configured for this application or request."); + throw new InvalidOperationException("Session has not been configured for this application " + + "or request."); } - if (feature.Session == null) + return feature.Session; + } + set + { + var feature = SessionFeature; + if (feature == null) { - if (feature.Factory == null) - { - throw new InvalidOperationException("No ISessionFactory available to create the ISession."); - } - feature.Session = feature.Factory.Create(); + feature = new DefaultSessionFeature(); + _session.Update(_features, feature); } - return new SessionCollection(feature.Session); + feature.Session = value; } } diff --git a/src/Microsoft.AspNet.Http/Features/DefaultSessionFeature.cs b/src/Microsoft.AspNet.Http/Features/DefaultSessionFeature.cs new file mode 100644 index 0000000000..041794c7b5 --- /dev/null +++ b/src/Microsoft.AspNet.Http/Features/DefaultSessionFeature.cs @@ -0,0 +1,14 @@ +// 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. + +namespace Microsoft.AspNet.Http.Features.Internal +{ + /// + /// This type exists only for the purpose of unit testing where the user can directly set the + /// property without the need for creating a . + /// + public class DefaultSessionFeature : ISessionFeature + { + public ISession Session { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Http/SessionCollection.cs b/src/Microsoft.AspNet.Http/SessionCollection.cs deleted file mode 100644 index 8123e2fb0b..0000000000 --- a/src/Microsoft.AspNet.Http/SessionCollection.cs +++ /dev/null @@ -1,74 +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 System.Collections; -using System.Collections.Generic; -using Microsoft.AspNet.Http.Features; - -namespace Microsoft.AspNet.Http.Internal -{ - public class SessionCollection : ISessionCollection - { - private readonly ISession _session; - - public SessionCollection(ISession session) - { - _session = session; - } - - public byte[] this[string key] - { - get - { - byte[] value; - TryGetValue(key, out value); - return value; - } - set - { - if (value == null) - { - Remove(key); - } - else - { - Set(key, new ArraySegment(value)); - } - } - } - - public bool TryGetValue(string key, out byte[] value) - { - return _session.TryGetValue(key, out value); - } - - public void Set(string key, ArraySegment value) - { - _session.Set(key, value); - } - - public void Remove(string key) - { - _session.Remove(key); - } - - public void Clear() - { - _session.Clear(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public IEnumerator> GetEnumerator() - { - foreach (var key in _session.Keys) - { - yield return new KeyValuePair(key, this[key]); - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/project.json b/src/Microsoft.AspNet.Http/project.json index 3334f37142..b8a689a4d2 100644 --- a/src/Microsoft.AspNet.Http/project.json +++ b/src/Microsoft.AspNet.Http/project.json @@ -4,7 +4,6 @@ "dependencies": { "Microsoft.AspNet.FeatureModel": "1.0.0-*", "Microsoft.AspNet.Http.Abstractions": "1.0.0-*", - "Microsoft.AspNet.Http.Features": "1.0.0-*", "Microsoft.AspNet.WebUtilities": "1.0.0-*", "Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" }, "Microsoft.Net.Http.Headers": "1.0.0-*" diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs index 0caea097ec..c8c1b0bc47 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs @@ -1,10 +1,13 @@ // 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 System.Collections.Generic; using System.Linq; using System.Security.Claims; +using System.Threading.Tasks; using Microsoft.AspNet.FeatureModel; +using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features.Internal; using Xunit; @@ -12,6 +15,69 @@ namespace Microsoft.AspNet.Http.Internal { public class DefaultHttpContextTests { + [Fact] + public void GetOnSessionProperty_ThrowsOnMissingSessionFeature() + { + // Arrange + var context = new DefaultHttpContext(); + + // Act & Assert + var exception = Assert.Throws(() => context.Session); + Assert.Equal("Session has not been configured for this application or request.", exception.Message); + } + + [Fact] + public void GetOnSessionProperty_ReturnsAvailableSession() + { + // Arrange + var context = new DefaultHttpContext(); + var session = new TestSession(); + session.Set("key1", null); + session.Set("key2", null); + var feature = new BlahSessionFeature(); + feature.Session = session; + context.SetFeature(feature); + + // Act & Assert + Assert.Same(session, context.Session); + context.Session.Set("key3", null); + Assert.Equal(3, context.Session.Keys.Count()); + } + + [Fact] + public void AllowsSettingSession_WithoutSettingUpSessionFeature_Upfront() + { + // Arrange + var session = new TestSession(); + var context = new DefaultHttpContext(); + + // Act + context.Session = session; + + // Assert + Assert.Same(session, context.Session); + } + + [Fact] + public void SettingSession_OverridesAvailableSession() + { + // Arrange + var context = new DefaultHttpContext(); + var session = new TestSession(); + session.Set("key1", null); + session.Set("key2", null); + var feature = new BlahSessionFeature(); + feature.Session = session; + context.SetFeature(feature); + + // Act + context.Session = new TestSession(); + + // Assert + Assert.NotSame(session, context.Session); + Assert.Empty(context.Session.Keys); + } + [Fact] public void EmptyUserIsNeverNull() { @@ -73,5 +139,48 @@ namespace Microsoft.AspNet.Http.Internal var context = new DefaultHttpContext(); return context; } + + private class TestSession : ISession + { + private Dictionary _store + = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public IEnumerable Keys { get { return _store.Keys; } } + + public void Clear() + { + _store.Clear(); + } + + public Task CommitAsync() + { + return Task.FromResult(0); + } + + public Task LoadAsync() + { + return Task.FromResult(0); + } + + public void Remove(string key) + { + _store.Remove(key); + } + + public void Set(string key, byte[] value) + { + _store[key] = value; + } + + public bool TryGetValue(string key, out byte[] value) + { + return _store.TryGetValue(key, out value); + } + } + + private class BlahSessionFeature : ISessionFeature + { + public ISession Session { get; set; } + } } } \ No newline at end of file