Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions samples/CoreApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
builder.Services.AddSystemWebAdapters()
.WrapAspNetCoreSession()
.AddSessionSerializer()
.AddCustomSerialization()
.AddJsonSessionSerializer(options =>
{
options.RegisterKey<int>("callCount");
});
.AddCustomSerialization();

builder.Services.AddDistributedMemoryCache();

Expand Down
19 changes: 18 additions & 1 deletion samples/CoreApp/SessionExampleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ internal static class SessionExampleExtensions

public static ISystemWebAdapterBuilder AddCustomSerialization(this ISystemWebAdapterBuilder builder)
{
builder.AddJsonSessionSerializer(options =>
{
options.RegisterKey<int>("callCount");
options.RegisterKey<int?>("item");
});

builder.Services.AddSingleton<ISessionKeySerializer>(new ByteArraySerializer(SessionKey));
return builder;
}
Expand Down Expand Up @@ -59,6 +65,17 @@ static void SetValue(HttpContext context, byte[] data)

return $"This endpoint has been hit {count} time(s) this session";
});


builder.MapGet("/item", (HttpContextCore ctx) =>
{
var context = (HttpContext)ctx;

var result = context.Session!["item"];
context.Session!["item"] = default(int);

return $"Current value: {result}";
});
}

/// <summary>
Expand Down Expand Up @@ -88,7 +105,7 @@ public bool TryDeserialize(string key, byte[] bytes, out object? obj)
return false;
}

public bool TrySerialize(string key, object value, out byte[] bytes)
public bool TrySerialize(string key, object? value, out byte[] bytes)
{
if (string.Equals(_key, key, StringComparison.Ordinal) && value is byte[] valueBytes)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public interface ISessionKeySerializer
/// <param name="value">Object to serialize.</param>,
/// <param name="bytes">Bytes if successful.</param>
/// <returns><c>true</c> if successful. If key is unknown, <c>false</c> will be returned.</returns>
bool TrySerialize(string key, object value, out byte[] bytes);
bool TrySerialize(string key, object? value, out byte[] bytes);

/// <summary>
/// Deserializes a session object for a given key.
Expand Down
9 changes: 1 addition & 8 deletions src/Services/SessionState/BinarySessionSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ public void Write(ISessionState state, BinaryWriter writer)
{
writer.Write(item);

if (state[item] is { } obj)
{
if (_serializer.TrySerialize(item, obj, out var result))
if (_serializer.TrySerialize(item, state[item], out var result))
{
writer.Write7BitEncodedInt(result.Length);
writer.Write(result);
Expand All @@ -66,11 +64,6 @@ public void Write(ISessionState state, BinaryWriter writer)
writer.Write7BitEncodedInt(0);
}
}
else
{
writer.Write7BitEncodedInt(0);
}
}

if (unknownKeys is null)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Services/SessionState/CompositeSessionKeySerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public CompositeSessionKeySerializer(IEnumerable<ISessionKeySerializer> serializ
_serializers = serializers.ToArray();
}

public bool TrySerialize(string key, object value, out byte[] bytes)
public bool TrySerialize(string key, object? value, out byte[] bytes)
{
foreach (var serializer in _serializers)
{
Expand Down
20 changes: 17 additions & 3 deletions src/Services/SessionState/JsonSessionKeySerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public JsonSessionKeySerializer(IOptions<JsonSessionSerializerOptions> options,
[LoggerMessage(0, LogLevel.Error, "Unexpected JSON serialize/deserialization error for '{Key}' expected type '{Type}'")]
private partial void LogException(Exception e, string key, string type);


[LoggerMessage(1, LogLevel.Warning, "Session key '{Key}' is registered as {RegisteredType} but was actually {FoundType}")]
private partial void UnexpectedType(string key, Type registeredType, Type foundType);

Expand All @@ -45,11 +44,20 @@ public bool TryDeserialize(string key, byte[] bytes, out object? obj)
return false;
}

public bool TrySerialize(string key, object value, out byte[] bytes)
public bool TrySerialize(string key, object? value, out byte[] bytes)
{
if (_options.Value.KnownKeys.TryGetValue(key, out var type))
{
if (type == value.GetType())
if (value is null)
{
if (!type.IsValueType || IsNullable(type))
{
// Create a new one instead of caching since technically the array values could be overwritten
bytes = "null"u8.ToArray();
return true;
}
}
else if (type == value.GetType() || IsNullableType(type, value.GetType()))
{
try
{
Expand All @@ -70,5 +78,11 @@ public bool TrySerialize(string key, object value, out byte[] bytes)
bytes = Array.Empty<byte>();
return false;
}

private static bool IsNullable(Type type)
=> type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);

private static bool IsNullableType(Type type, Type nullableArg)
=> IsNullable(type) && nullableArg == type.GenericTypeArguments[0];
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -174,6 +172,28 @@ public async Task DeserializeIsReadOnly()
Assert.Empty(result.Keys);
}

[Fact]
public async Task DeserializeIsReadOnlyEmptyNull()
{
// Arrange
var data = new byte[] { 1, 2, 105, 100, 0, 0, 1, 0, 0, 0 };
using var ms = new MemoryStream(data);

var serializer = CreateSerializer();

// Act
var result = await serializer.DeserializeAsync(ms, default);

// Assert
Assert.Equal("id", result!.SessionID);
Assert.True(result.IsReadOnly);
Assert.False(result.IsAbandoned);
Assert.False(result.IsNewSession);
Assert.Equal(0, result.Timeout);
Assert.Equal(0, result.Count);
Assert.Empty(result.Keys);
}

[Fact]
public async Task SerializeTimeout()
{
Expand Down Expand Up @@ -239,6 +259,110 @@ public async Task Serialize1Key()
Assert.Equal(ms.ToArray(), new byte[] { 1, 2, 105, 100, 0, 0, 0, 0, 1, 4, 107, 101, 121, 49, 1, 42, 0 });
}

[Fact]
public async Task Serialize1KeyNull()
{
// Arrange
var obj = default(object);
var state = new Mock<ISessionState>();
state.Setup(s => s["key1"]).Returns(obj);
state.Setup(s => s.SessionID).Returns("id");
state.Setup(s => s.Keys).Returns(new[] { "key1" });
state.Setup(s => s.Count).Returns(1);

var keySerializer = new Mock<ISessionKeySerializer>();
var bytes = new byte[] { 0 };
keySerializer.Setup(k => k.TrySerialize("key1", obj, out bytes)).Returns(true);

var serializer = CreateSerializer(keySerializer.Object);
using var ms = new MemoryStream();

// Act
await serializer.SerializeAsync(state.Object, ms, default);

// Assert
Assert.Equal(ms.ToArray(), new byte[] { 1, 2, 105, 100, 0, 0, 0, 0, 1, 4, 107, 101, 121, 49, 1, 0, 0 });
}

[Fact]
public async Task Deserialize1KeyNull()
{
// Arrange
var data = new byte[] { 1, 2, 105, 100, 0, 0, 0, 0, 1, 4, 107, 101, 121, 49, 1, 0, 0 };
var obj = new object();
var value = new byte[] { 0 };

var keySerializer = new Mock<ISessionKeySerializer>();
keySerializer.Setup(k => k.TryDeserialize("key1", value, out obj)).Returns(true);

var serializer = CreateSerializer(keySerializer.Object);

// Act
var result = await serializer.DeserializeAsync(new MemoryStream(data), default);

// Assert
Assert.Equal("id", result!.SessionID);
Assert.False(result.IsReadOnly);
Assert.False(result.IsAbandoned);
Assert.False(result.IsNewSession);
Assert.Equal(0, result.Timeout);
Assert.Equal(1, result.Count);
Assert.Same(obj, result["key1"]);
Assert.Collection(result.Keys, k => Assert.Equal("key1", k));
}

[Fact]
public async Task Deserialize1KeyV1()
{
// Arrange
var obj = new object();
var keySerializer = new Mock<ISessionKeySerializer>();
keySerializer.Setup(k => k.TryDeserialize("key1", Array.Empty<byte>(), out obj)).Returns(true);

var data = new byte[] { 1, 2, 105, 100, 0, 0, 0, 0, 1, 4, 107, 101, 121, 49, 0, 0 };
using var ms = new MemoryStream(data);

var serializer = CreateSerializer(keySerializer.Object);

// Act
var result = await serializer.DeserializeAsync(ms, default);

// Assert
Assert.Equal("id", result!.SessionID);
Assert.False(result.IsReadOnly);
Assert.False(result.IsAbandoned);
Assert.False(result.IsNewSession);
Assert.Equal(0, result.Timeout);
Assert.Equal(1, result.Count);
Assert.Equal(result.Keys, new[] { "key1" });
Assert.Equal(obj, result["key1"]);
}

[Fact]
public async Task Serialize1KeyNullable()
{
// Arrange
var obj = (int?)5;
var state = new Mock<ISessionState>();
state.Setup(s => s["key1"]).Returns(obj);
state.Setup(s => s.SessionID).Returns("id");
state.Setup(s => s.Keys).Returns(new[] { "key1" });
state.Setup(s => s.Count).Returns(1);

var keySerializer = new Mock<ISessionKeySerializer>();
var bytes = new byte[] { 0 };
keySerializer.Setup(k => k.TrySerialize("key1", obj, out bytes)).Returns(true);

var serializer = CreateSerializer(keySerializer.Object);
using var ms = new MemoryStream();

// Act
await serializer.SerializeAsync(state.Object, ms, default);

// Assert
Assert.Equal(ms.ToArray(), new byte[] { 1, 2, 105, 100, 0, 0, 0, 0, 1, 4, 107, 101, 121, 49, 1, 0, 0 });
}

[Fact]
public async Task Deserialize1Key()
{
Expand Down Expand Up @@ -290,7 +414,7 @@ public Composite(ISessionKeySerializer serializer)
public bool TryDeserialize(string key, byte[] bytes, out object? obj)
=> _serializer.TryDeserialize(key, bytes, out obj);

public bool TrySerialize(string key, object value, out byte[] bytes)
public bool TrySerialize(string key, object? value, out byte[] bytes)
=> _serializer.TrySerialize(key, value, out bytes);
}
}
Loading