File: Protocol\MessagePackHubProtocol.cs
Web Access
Project: src\src\SignalR\common\Protocols.MessagePack\src\Microsoft.AspNetCore.SignalR.Protocols.MessagePack.csproj (Microsoft.AspNetCore.SignalR.Protocols.MessagePack)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using MessagePack;
using MessagePack.Formatters;
using MessagePack.Resolvers;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Shared;
using Microsoft.Extensions.Options;
 
namespace Microsoft.AspNetCore.SignalR.Protocol;
 
/// <summary>
/// Implements the SignalR Hub Protocol using MessagePack.
/// </summary>
public class MessagePackHubProtocol : IHubProtocol
{
    private const string ProtocolName = "messagepack";
    private const int ProtocolVersion = 2;
    private readonly DefaultMessagePackHubProtocolWorker _worker;
 
    /// <inheritdoc />
    public string Name => ProtocolName;
 
    /// <inheritdoc />
    public int Version => ProtocolVersion;
 
    /// <inheritdoc />
    public TransferFormat TransferFormat => TransferFormat.Binary;
 
    /// <summary>
    /// Initializes a new instance of the <see cref="MessagePackHubProtocol"/> class.
    /// </summary>
    public MessagePackHubProtocol()
        : this(Options.Create(new MessagePackHubProtocolOptions()))
    { }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="MessagePackHubProtocol"/> class.
    /// </summary>
    /// <param name="options">The options used to initialize the protocol.</param>
    public MessagePackHubProtocol(IOptions<MessagePackHubProtocolOptions> options)
    {
        ArgumentNullThrowHelper.ThrowIfNull(options);
 
        _worker = new DefaultMessagePackHubProtocolWorker(options.Value.SerializerOptions);
    }
 
    /// <inheritdoc />
    public bool IsVersionSupported(int version)
    {
        return version <= Version;
    }
 
    /// <inheritdoc />
    public bool TryParseMessage(ref ReadOnlySequence<byte> input, IInvocationBinder binder, [NotNullWhen(true)] out HubMessage? message)
        => _worker.TryParseMessage(ref input, binder, out message);
 
    /// <inheritdoc />
    public void WriteMessage(HubMessage message, IBufferWriter<byte> output)
        => _worker.WriteMessage(message, output);
 
    /// <inheritdoc />
    public ReadOnlyMemory<byte> GetMessageBytes(HubMessage message)
        => _worker.GetMessageBytes(message);
 
    internal static MessagePackSerializerOptions CreateDefaultMessagePackSerializerOptions() =>
        MessagePackSerializerOptions
            .Standard
            .WithResolver(SignalRResolver.Instance)
            .WithSecurity(MessagePackSecurity.UntrustedData);
 
    internal sealed class SignalRResolver : IFormatterResolver
    {
        public static readonly IFormatterResolver Instance = new SignalRResolver();
 
        public static readonly IReadOnlyList<IFormatterResolver> Resolvers = new IFormatterResolver[]
        {
                DynamicEnumAsStringResolver.Instance,
                ContractlessStandardResolver.Instance,
        };
 
        public IMessagePackFormatter<T>? GetFormatter<T>()
        {
            return Cache<T>.Formatter;
        }
 
        private static class Cache<T>
        {
            public static readonly IMessagePackFormatter<T>? Formatter = ResolveFormatter();
 
            private static IMessagePackFormatter<T>? ResolveFormatter()
            {
                foreach (var resolver in Resolvers)
                {
                    var formatter = resolver.GetFormatter<T>();
                    if (formatter != null)
                    {
                        return formatter;
                    }
                }
 
                return null;
            }
        }
    }
}