Skip to content

Allow multiple ISessionKeySerializer registrations to be used #321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 13, 2023
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
18 changes: 3 additions & 15 deletions samples/CoreApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using Microsoft.AspNetCore.SystemWebAdapters;

var builder = WebApplication.CreateBuilder();

builder.Services.AddSystemWebAdapters()
.WrapAspNetCoreSession()
.AddSessionSerializer()
.AddCustomSerialization()
.AddJsonSessionSerializer(options =>
{
options.RegisterKey<int>("callCount");
Expand All @@ -30,18 +29,7 @@

app.UseSystemWebAdapters();

app.MapGet("/session", (HttpContext context) =>
{
var adapter = (System.Web.HttpContext)context;

if (adapter.Session!["callCount"] is not int count)
{
count = 0;
}

adapter.Session!["callCount"] = ++count;

return $"This endpoint has been hit {count} time(s) this session";
}).RequireSystemWebAdapterSession();
app.MapGroup("/session")
.MapSessionExample();

app.Run();
103 changes: 103 additions & 0 deletions samples/CoreApp/SessionExampleExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Microsoft.AspNetCore.SystemWebAdapters;
using Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization;

using HttpContext = System.Web.HttpContext;
using HttpContextCore = Microsoft.AspNetCore.Http.HttpContext;

internal static class SessionExampleExtensions
{
private const string SessionKey = "array";

public static ISystemWebAdapterBuilder AddCustomSerialization(this ISystemWebAdapterBuilder builder)
{
builder.Services.AddSingleton<ISessionKeySerializer>(new ByteArraySerializer(SessionKey));
return builder;
}

public static void MapSessionExample(this RouteGroupBuilder builder)
{
builder.RequireSystemWebAdapterSession();

builder.MapGet("/custom", (HttpContextCore ctx) =>
{
return GetValue(ctx);

static object? GetValue(HttpContext context)
{
if (context.Session![SessionKey] is { } existing)
{
return existing;
}

var temp = new byte[] { 1, 2, 3 };
context.Session[SessionKey] = temp;
return temp;
}
});

builder.MapPost("/custom", async (HttpContextCore ctx) =>
{
using var ms = new MemoryStream();
await ctx.Request.Body.CopyToAsync(ms);

SetValue(ctx, ms.ToArray());

static void SetValue(HttpContext context, byte[] data)
=> context.Session![SessionKey] = data;
});

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

if (context.Session!["callCount"] is not int count)
{
count = 0;
}

context.Session!["callCount"] = ++count;

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

/// <summary>
/// This is an example of a custom <see cref="ISessionKeySerializer"/> that takes a key name and expectes the value to be a byte array.
/// This shows how to implement a custom serializer, which can then be registered in the DI container. When the session middleware
/// attempts to serialize/deserialize keys, it will cycle through all the registered <see cref="ISessionKeySerializer"/> instances until
/// it finds one that can handle the given key.
/// </summary>
private sealed class ByteArraySerializer : ISessionKeySerializer
{
private readonly string _key;

public ByteArraySerializer(string key)
{
_key = key;
}

public bool TryDeserialize(string key, byte[] bytes, out object? obj)
{
if (string.Equals(_key, key, StringComparison.Ordinal))
{
obj = bytes;
return true;
}

obj = null;
return false;
}

public bool TrySerialize(string key, object value, out byte[] bytes)
{
if (string.Equals(_key, key, StringComparison.Ordinal) && value is byte[] valueBytes)
{
bytes = valueBytes;
return true;
}

bytes = Array.Empty<byte>();
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization;
using Microsoft.Extensions.Logging;
Expand All @@ -14,7 +16,7 @@ internal class AspNetCoreSessionManager : ISessionManager
private readonly ILoggerFactory _loggerFactory;
private readonly IOptions<SessionSerializerOptions> _options;

public AspNetCoreSessionManager(ISessionKeySerializer serializer, ILoggerFactory loggerFactory, IOptions<SessionSerializerOptions> options)
public AspNetCoreSessionManager(ICompositeSessionKeySerializer serializer, ILoggerFactory loggerFactory, IOptions<SessionSerializerOptions> options)
{
_serializer = serializer;
_loggerFactory = loggerFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.SystemWebAdapters;
using Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization;
using Microsoft.AspNetCore.SystemWebAdapters.SessionState.Wrapped;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.DependencyInjection;

Expand All @@ -23,6 +25,7 @@ public static ISystemWebAdapterBuilder WrapAspNetCoreSession(this ISystemWebAdap
builder.Services.AddSession(options);
}

builder.Services.TryAddSingleton<ICompositeSessionKeySerializer, CompositeSessionKeySerializer>();
builder.Services.AddSingleton<ISessionManager, AspNetCoreSessionManager>();

return builder;
Expand Down
3 changes: 1 addition & 2 deletions src/Services/SessionState/BinarySessionSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ internal partial class BinarySessionSerializer : ISessionSerializer

private readonly SessionSerializerOptions _options;
private readonly ISessionKeySerializer _serializer;

private readonly ILogger<BinarySessionSerializer> _logger;

public BinarySessionSerializer(ISessionKeySerializer serializer, IOptions<SessionSerializerOptions> options, ILogger<BinarySessionSerializer> logger)
public BinarySessionSerializer(ICompositeSessionKeySerializer serializer, IOptions<SessionSerializerOptions> options, ILogger<BinarySessionSerializer> logger)
{
_serializer = serializer;
_options = options.Value;
Expand Down
43 changes: 43 additions & 0 deletions src/Services/SessionState/CompositeSessionKeySerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization;

internal sealed class CompositeSessionKeySerializer : ICompositeSessionKeySerializer
{
private readonly ISessionKeySerializer[] _serializers;

public CompositeSessionKeySerializer(IEnumerable<ISessionKeySerializer> serializers)
{
_serializers = serializers.ToArray();
}

public bool TrySerialize(string key, object value, out byte[] bytes)
{
foreach (var serializer in _serializers)
{
if (serializer.TrySerialize(key, value, out bytes))
{
return true;
}
}

bytes = Array.Empty<byte>();
return false;
}

public bool TryDeserialize(string key, byte[] bytes, out object? obj)
{
foreach (var serializer in _serializers)
{
if (serializer.TryDeserialize(key, bytes, out obj))
{
return true;
}
}

obj = null;
return false;
}
}
5 changes: 5 additions & 0 deletions src/Services/SessionState/ICompositeSessionKeySerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization;

internal interface ICompositeSessionKeySerializer : ISessionKeySerializer
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static ISystemWebAdapterBuilder AddJsonSessionSerializer(this ISystemWebA
}

builder.AddSessionSerializer();
builder.Services.TryAddSingleton<ISessionKeySerializer, JsonSessionKeySerializer>();
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ISessionKeySerializer, JsonSessionKeySerializer>());

var options = builder.Services.AddOptions<JsonSessionSerializerOptions>();

Expand Down
1 change: 1 addition & 0 deletions src/Services/SessionState/SessionSerializerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public static ISystemWebAdapterBuilder AddSessionSerializer(this ISystemWebAdapt
options.Configure(configure);
}

builder.Services.TryAddSingleton<ICompositeSessionKeySerializer, CompositeSessionKeySerializer>();
builder.Services.TryAddSingleton<ISessionSerializer, BinarySessionSerializer>();

return builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,27 @@ public async Task Deserialize1Key()
private static BinarySessionSerializer CreateSerializer(ISessionKeySerializer? keySerializer = null)
{
keySerializer ??= new Mock<ISessionKeySerializer>().Object;

var logger = new Mock<ILogger<BinarySessionSerializer>>();

var optionContainer = new Mock<IOptions<SessionSerializerOptions>>();
optionContainer.Setup(o => o.Value).Returns(new SessionSerializerOptions());

return new BinarySessionSerializer(keySerializer, optionContainer.Object, logger.Object);
return new BinarySessionSerializer(new Composite(keySerializer), optionContainer.Object, logger.Object);
}

private sealed class Composite : ICompositeSessionKeySerializer
{
private readonly ISessionKeySerializer _serializer;

public Composite(ISessionKeySerializer serializer)
{
_serializer = 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)
=> _serializer.TrySerialize(key, value, out bytes);
}
}
Loading