Session api review changes

This commit is contained in:
Kiran Challa 2015-05-27 15:12:43 -07:00
parent 15d8905ecc
commit dab08ba7e9
12 changed files with 111 additions and 163 deletions

View File

@ -1,6 +1,10 @@
using System; // 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 Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.Framework.Caching.Redis;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
@ -15,16 +19,24 @@ namespace SessionSample
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
// Adds a default in-memory implementation of IDistributedCache
services.AddCaching(); services.AddCaching();
// Uncomment the following line to use the Redis implementation of IDistributedCache.
// This will override any previously registered IDistributedCache service.
//services.AddTransient<IDistributedCache, RedisCache>();
services.AddSession(); services.AddSession();
services.ConfigureSession(o =>
{
o.IdleTimeout = TimeSpan.FromSeconds(30);
});
} }
public void Configure(IApplicationBuilder app) public void Configure(IApplicationBuilder app)
{ {
app.UseSession(o => { app.UseSession();
o.IdleTimeout = TimeSpan.FromSeconds(30); });
// app.UseInMemorySession();
// app.UseDistributedSession(new RedisCache(new RedisCacheOptions() { Configuration = "localhost" }));
app.Map("/session", subApp => app.Map("/session", subApp =>
{ {

View File

@ -2,10 +2,10 @@
"webroot": "wwwroot", "webroot": "wwwroot",
"exclude": "wwwroot/**/*.*", "exclude": "wwwroot/**/*.*",
"dependencies": { "dependencies": {
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
"Microsoft.AspNet.Server.IIS": "1.0.0-*", "Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener" : "1.0.0-*", "Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.AspNet.Session": "1.0.0-*", "Microsoft.AspNet.Session": "1.0.0-*",
"Microsoft.Framework.Caching.Memory": "1.0.0-*",
"Microsoft.Framework.Caching.Redis": "1.0.0-*", "Microsoft.Framework.Caching.Redis": "1.0.0-*",
"Microsoft.Framework.Logging.Console": "1.0.0-*" "Microsoft.Framework.Logging.Console": "1.0.0-*"
}, },

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Microsoft.AspNet.Http; using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features;
using Microsoft.Framework.Caching.Distributed; using Microsoft.Framework.Caching.Distributed;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
@ -56,7 +56,7 @@ namespace Microsoft.AspNet.Session
return _store.TryGetValue(new EncodedKey(key), out value); return _store.TryGetValue(new EncodedKey(key), out value);
} }
public void Set(string key, ArraySegment<byte> value) public void Set(string key, [NotNull] byte[] value)
{ {
var encodedKey = new EncodedKey(key); var encodedKey = new EncodedKey(key);
if (encodedKey.KeyBytes.Length > KeyLengthLimit) if (encodedKey.KeyBytes.Length > KeyLengthLimit)
@ -64,10 +64,6 @@ namespace Microsoft.AspNet.Session
throw new ArgumentOutOfRangeException(nameof(key), throw new ArgumentOutOfRangeException(nameof(key),
string.Format("The key cannot be longer than '{0}' when encoded with UTF-8.", KeyLengthLimit)); string.Format("The key cannot be longer than '{0}' when encoded with UTF-8.", KeyLengthLimit));
} }
if (value.Array == null)
{
throw new ArgumentException("The ArraySegment<byte>.Array cannot be null.", nameof(value));
}
Load(); Load();
if (!_tryEstablishSession()) if (!_tryEstablishSession())
@ -75,8 +71,8 @@ namespace Microsoft.AspNet.Session
throw new InvalidOperationException("The session cannot be established after the response has started."); throw new InvalidOperationException("The session cannot be established after the response has started.");
} }
_isModified = true; _isModified = true;
byte[] copy = new byte[value.Count]; byte[] copy = new byte[value.Length];
Buffer.BlockCopy(value.Array, value.Offset, copy, 0, value.Count); Buffer.BlockCopy(src: value, srcOffset: 0, dst: copy, dstOffset: 0, count: value.Length);
_store[encodedKey] = copy; _store[encodedKey] = copy;
} }
@ -93,12 +89,21 @@ namespace Microsoft.AspNet.Session
_store.Clear(); _store.Clear();
} }
// TODO: This should throw if called directly, but most other places it should fail silently (e.g. TryGetValue should just return null). private void Load()
public void Load()
{ {
if (!_loaded) if (!_loaded)
{ {
var data = _cache.Get(_sessionId); LoadAsync().GetAwaiter().GetResult();
}
}
// TODO: This should throw if called directly, but most other places it should fail silently
// (e.g. TryGetValue should just return null).
public async Task LoadAsync()
{
if (!_loaded)
{
var data = await _cache.GetAsync(_sessionId);
if (data != null) if (data != null)
{ {
Deserialize(new MemoryStream(data)); Deserialize(new MemoryStream(data));
@ -111,11 +116,11 @@ namespace Microsoft.AspNet.Session
} }
} }
public void Commit() public async Task CommitAsync()
{ {
if (_isModified) if (_isModified)
{ {
var data = _cache.Get(_sessionId); var data = await _cache.GetAsync(_sessionId);
if (_logger.IsEnabled(LogLevel.Information) && data == null) if (_logger.IsEnabled(LogLevel.Information) && data == null)
{ {
_logger.LogInformation("Session {0} started", _sessionId); _logger.LogInformation("Session {0} started", _sessionId);
@ -124,7 +129,7 @@ namespace Microsoft.AspNet.Session
var stream = new MemoryStream(); var stream = new MemoryStream();
Serialize(stream); Serialize(stream);
_cache.Set( await _cache.SetAsync(
_sessionId, _sessionId,
stream.ToArray(), stream.ToArray(),
new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout)); new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout));

View File

@ -1,37 +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 Microsoft.AspNet.Http.Features;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Session
{
public class SessionFactory : ISessionFactory
{
private readonly string _sessionKey;
private readonly ISessionStore _store;
private readonly TimeSpan _idleTimeout;
private readonly Func<bool> _tryEstablishSession;
private readonly bool _isNewSessionKey;
public SessionFactory([NotNull] string sessionKey, [NotNull] ISessionStore store, TimeSpan idleTimeout, [NotNull] Func<bool> tryEstablishSession, bool isNewSessionKey)
{
_sessionKey = sessionKey;
_store = store;
_idleTimeout = idleTimeout;
_tryEstablishSession = tryEstablishSession;
_isNewSessionKey = isNewSessionKey;
}
public bool IsAvailable
{
get { return _store.IsAvailable; }
}
public ISession Create()
{
return _store.Create(_sessionKey, _idleTimeout, _tryEstablishSession, _isNewSessionKey);
}
}
}

View File

@ -7,8 +7,6 @@ namespace Microsoft.AspNet.Session
{ {
public class SessionFeature : ISessionFeature public class SessionFeature : ISessionFeature
{ {
public ISessionFactory Factory { get; set; }
public ISession Session { get; set; } public ISession Session { get; set; }
} }
} }

View File

@ -23,36 +23,19 @@ namespace Microsoft.AspNet.Session
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly SessionOptions _options; private readonly SessionOptions _options;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ISessionStore _sessionStore;
public SessionMiddleware( public SessionMiddleware(
[NotNull] RequestDelegate next, [NotNull] RequestDelegate next,
[NotNull] ILoggerFactory loggerFactory, [NotNull] ILoggerFactory loggerFactory,
[NotNull] IEnumerable<ISessionStore> sessionStore, [NotNull] ISessionStore sessionStore,
[NotNull] IOptions<SessionOptions> options, [NotNull] IOptions<SessionOptions> options)
[NotNull] ConfigureOptions<SessionOptions> configureOptions)
{ {
_next = next; _next = next;
_logger = loggerFactory.CreateLogger<SessionMiddleware>(); _logger = loggerFactory.CreateLogger<SessionMiddleware>();
if (configureOptions != null) _options = options.Options;
{ _sessionStore = sessionStore;
_options = options.GetNamedOptions(configureOptions.Name); _sessionStore.Connect();
configureOptions.Configure(_options);
}
else
{
_options = options.Options;
}
if (_options.Store == null)
{
_options.Store = sessionStore.FirstOrDefault();
if (_options.Store == null)
{
throw new ArgumentException("ISessionStore must be specified.");
}
}
_options.Store.Connect();
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
@ -72,8 +55,7 @@ namespace Microsoft.AspNet.Session
} }
var feature = new SessionFeature(); var feature = new SessionFeature();
feature.Factory = new SessionFactory(sessionKey, _options.Store, _options.IdleTimeout, tryEstablishSession, isNewSessionKey); feature.Session = _sessionStore.Create(sessionKey, _options.IdleTimeout, tryEstablishSession, isNewSessionKey);
feature.Session = feature.Factory.Create();
context.SetFeature<ISessionFeature>(feature); context.SetFeature<ISessionFeature>(feature);
try try
@ -88,7 +70,7 @@ namespace Microsoft.AspNet.Session
{ {
try try
{ {
feature.Session.Commit(); await feature.Session.CommitAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -1,45 +1,16 @@
// 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Session; using Microsoft.AspNet.Session;
using Microsoft.Framework.Caching.Distributed;
using Microsoft.Framework.Caching.Memory;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Builder namespace Microsoft.AspNet.Builder
{ {
public static class SessionMiddlewareExtensions public static class SessionMiddlewareExtensions
{ {
public static IApplicationBuilder UseInMemorySession([NotNull] this IApplicationBuilder app, IMemoryCache cache = null, Action<SessionOptions> configure = null) public static IApplicationBuilder UseSession([NotNull] this IApplicationBuilder app)
{ {
return app.UseDistributedSession(new LocalCache(cache ?? new MemoryCache(new MemoryCacheOptions())), configure); return app.UseMiddleware<SessionMiddleware>();
}
public static IApplicationBuilder UseDistributedSession([NotNull] this IApplicationBuilder app,
IDistributedCache cache, Action<SessionOptions> configure = null)
{
var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
return app.UseSession(options =>
{
options.Store = new DistributedSessionStore(cache, loggerFactory);
if (configure != null)
{
configure(options);
}
});
}
public static IApplicationBuilder UseSession([NotNull] this IApplicationBuilder app, Action<SessionOptions> configure = null)
{
return app.UseMiddleware<SessionMiddleware>(
new ConfigureOptions<SessionOptions>(configure ?? (o => { }))
{
Name = string.Empty
});
} }
} }
} }

View File

@ -34,10 +34,5 @@ namespace Microsoft.AspNet.Session
/// resets the timeout. Note this only applies to the content of the session, not the cookie. /// resets the timeout. Note this only applies to the content of the session, not the cookie.
/// </summary> /// </summary>
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(20); public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(20);
/// <summary>
/// Gets or sets the session storage manager. This overrides any session store passed into the middleware constructor.
/// </summary>
public ISessionStore Store { get; set; }
} }
} }

View File

@ -11,19 +11,16 @@ namespace Microsoft.Framework.DependencyInjection
{ {
public static IServiceCollection AddSession([NotNull] this IServiceCollection services) public static IServiceCollection AddSession([NotNull] this IServiceCollection services)
{ {
return services.AddSession(configure: null); services.AddOptions();
services.AddTransient<ISessionStore, DistributedSessionStore>();
return services;
} }
public static IServiceCollection AddSession([NotNull] this IServiceCollection services, Action<SessionOptions> configure) public static void ConfigureSession(
[NotNull] this IServiceCollection services,
[NotNull] Action<SessionOptions> configure)
{ {
services.AddTransient<ISessionStore, DistributedSessionStore>(); services.Configure(configure);
if (configure != null)
{
services.Configure(configure);
}
return services;
} }
} }
} }

View File

@ -2,10 +2,11 @@
"version": "1.0.0-*", "version": "1.0.0-*",
"description": "ASP.NET 5 session state middleware.", "description": "ASP.NET 5 session state middleware.",
"dependencies": { "dependencies": {
"Microsoft.AspNet.Http.Extensions": "1.0.0-*", "Microsoft.AspNet.Http.Abstractions": "1.0.0-*",
"Microsoft.Framework.Caching.Memory": "1.0.0-*", "Microsoft.Framework.Caching.Abstractions": "1.0.0-*",
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*", "Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
"Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" } "Microsoft.Framework.NotNullAttribute.Sources": { "type": "build", "version": "1.0.0-*" },
"Microsoft.Framework.OptionsModel": "1.0.0-*"
}, },
"compilationOptions": { "compilationOptions": {
"allowUnsafe": true "allowUnsafe": true

View File

@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Session;
using Microsoft.AspNet.TestHost; using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
@ -24,14 +25,19 @@ namespace Microsoft.AspNet.Session
{ {
using (var server = TestServer.Create(app => using (var server = TestServer.Create(app =>
{ {
app.UseInMemorySession(); app.UseSession();
app.Run(context => app.Run(context =>
{ {
Assert.Null(context.Session.GetString("NotFound")); Assert.Null(context.Session.GetString("NotFound"));
return Task.FromResult(0); return Task.FromResult(0);
}); });
}, },
services => services.AddOptions())) services =>
{
services.AddCaching();
services.AddSession();
}))
{ {
var client = server.CreateClient(); var client = server.CreateClient();
var response = await client.GetAsync(string.Empty); var response = await client.GetAsync(string.Empty);
@ -46,7 +52,7 @@ namespace Microsoft.AspNet.Session
{ {
using (var server = TestServer.Create(app => using (var server = TestServer.Create(app =>
{ {
app.UseInMemorySession(); app.UseSession();
app.Run(context => app.Run(context =>
{ {
Assert.Null(context.Session.GetString("Key")); Assert.Null(context.Session.GetString("Key"));
@ -55,7 +61,11 @@ namespace Microsoft.AspNet.Session
return Task.FromResult(0); return Task.FromResult(0);
}); });
}, },
services => services.AddOptions())) services =>
{
services.AddCaching();
services.AddSession();
}))
{ {
var client = server.CreateClient(); var client = server.CreateClient();
var response = await client.GetAsync(string.Empty); var response = await client.GetAsync(string.Empty);
@ -72,7 +82,7 @@ namespace Microsoft.AspNet.Session
{ {
using (var server = TestServer.Create(app => using (var server = TestServer.Create(app =>
{ {
app.UseInMemorySession(); app.UseSession();
app.Run(context => app.Run(context =>
{ {
int? value = context.Session.GetInt32("Key"); int? value = context.Session.GetInt32("Key");
@ -86,7 +96,11 @@ namespace Microsoft.AspNet.Session
return context.Response.WriteAsync(value.Value.ToString()); return context.Response.WriteAsync(value.Value.ToString());
}); });
}, },
services => services.AddOptions())) services =>
{
services.AddCaching();
services.AddSession();
}))
{ {
var client = server.CreateClient(); var client = server.CreateClient();
var response = await client.GetAsync("first"); var response = await client.GetAsync("first");
@ -107,7 +121,7 @@ namespace Microsoft.AspNet.Session
{ {
using (var server = TestServer.Create(app => using (var server = TestServer.Create(app =>
{ {
app.UseInMemorySession(); app.UseSession();
app.Run(context => app.Run(context =>
{ {
int? value = context.Session.GetInt32("Key"); int? value = context.Session.GetInt32("Key");
@ -131,7 +145,11 @@ namespace Microsoft.AspNet.Session
return context.Response.WriteAsync(value.Value.ToString()); return context.Response.WriteAsync(value.Value.ToString());
}); });
}, },
services => services.AddOptions())) services =>
{
services.AddCaching();
services.AddSession();
}))
{ {
var client = server.CreateClient(); var client = server.CreateClient();
var response = await client.GetAsync("first"); var response = await client.GetAsync("first");
@ -151,7 +169,7 @@ namespace Microsoft.AspNet.Session
{ {
using (var server = TestServer.Create(app => using (var server = TestServer.Create(app =>
{ {
app.UseInMemorySession(); app.UseSession();
app.Run(context => app.Run(context =>
{ {
int? value = context.Session.GetInt32("Key"); int? value = context.Session.GetInt32("Key");
@ -175,7 +193,11 @@ namespace Microsoft.AspNet.Session
return context.Response.WriteAsync(value.Value.ToString()); return context.Response.WriteAsync(value.Value.ToString());
}); });
}, },
services => services.AddOptions())) services =>
{
services.AddCaching();
services.AddSession();
}))
{ {
var client = server.CreateClient(); var client = server.CreateClient();
var response = await client.GetAsync("first"); var response = await client.GetAsync("first");
@ -197,7 +219,7 @@ namespace Microsoft.AspNet.Session
var loggerFactory = new TestLoggerFactory(sink, enabled: true); var loggerFactory = new TestLoggerFactory(sink, enabled: true);
using (var server = TestServer.Create(app => using (var server = TestServer.Create(app =>
{ {
app.UseInMemorySession(); app.UseSession();
app.Run(context => app.Run(context =>
{ {
context.Session.SetString("Key", "Value"); context.Session.SetString("Key", "Value");
@ -206,8 +228,9 @@ namespace Microsoft.AspNet.Session
}, },
services => services =>
{ {
services.AddOptions();
services.AddInstance(typeof(ILoggerFactory), loggerFactory); services.AddInstance(typeof(ILoggerFactory), loggerFactory);
services.AddCaching();
services.AddSession();
})) }))
{ {
var client = server.CreateClient(); var client = server.CreateClient();
@ -226,9 +249,7 @@ namespace Microsoft.AspNet.Session
var loggerFactory = new TestLoggerFactory(sink, enabled: true); var loggerFactory = new TestLoggerFactory(sink, enabled: true);
using (var server = TestServer.Create(app => using (var server = TestServer.Create(app =>
{ {
app.UseInMemorySession(configure: o => { app.UseSession();
o.IdleTimeout = TimeSpan.FromMilliseconds(30);
});
app.Run(context => app.Run(context =>
{ {
int? value = context.Session.GetInt32("Key"); int? value = context.Session.GetInt32("Key");
@ -248,8 +269,10 @@ namespace Microsoft.AspNet.Session
}, },
services => services =>
{ {
services.AddOptions();
services.AddInstance(typeof(ILoggerFactory), loggerFactory); services.AddInstance(typeof(ILoggerFactory), loggerFactory);
services.AddCaching();
services.AddSession();
services.ConfigureSession(o => o.IdleTimeout = TimeSpan.FromMilliseconds(30));
})) }))
{ {
var client = server.CreateClient(); var client = server.CreateClient();

View File

@ -1,14 +1,15 @@
{ {
"dependencies": { "dependencies": {
"Microsoft.AspNet.Session": "1.0.0-*", "Microsoft.AspNet.Session": "1.0.0-*",
"Microsoft.AspNet.TestHost": "1.0.0-*", "Microsoft.AspNet.TestHost": "1.0.0-*",
"Microsoft.Framework.Logging.Testing": "1.0.0-*", "Microsoft.Framework.Caching.Memory": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*" "Microsoft.Framework.Logging.Testing": "1.0.0-*",
}, "xunit.runner.aspnet": "2.0.0-aspnet-*"
"commands": { },
"test": "xunit.runner.aspnet" "commands": {
}, "test": "xunit.runner.aspnet"
"frameworks": { },
"dnx451": {} "frameworks": {
} "dnx451": { }
}
} }