diff --git a/src/Microsoft.AspNetCore.Blazor.Server/Circuits/MessagePackBinaryBlockStream.cs b/src/Microsoft.AspNetCore.Blazor.Server/Circuits/MessagePackBufferStream.cs
similarity index 55%
rename from src/Microsoft.AspNetCore.Blazor.Server/Circuits/MessagePackBinaryBlockStream.cs
rename to src/Microsoft.AspNetCore.Blazor.Server/Circuits/MessagePackBufferStream.cs
index 6d0b0b15ab..f3a6e0a33f 100644
--- a/src/Microsoft.AspNetCore.Blazor.Server/Circuits/MessagePackBinaryBlockStream.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Server/Circuits/MessagePackBufferStream.cs
@@ -8,28 +8,19 @@ using System.IO;
namespace Microsoft.AspNetCore.Blazor.Server.Circuits
{
///
- /// A write-only stream that outputs its data to an underlying expandable
- /// buffer in the format for a MessagePack 'Bin32' block. Supports writing
- /// into buffers up to 2GB in length.
+ /// Provides Stream APIs for writing to a MessagePack-supplied expandable buffer.
///
- internal class MessagePackBinaryBlockStream : Stream
+ internal class MessagePackBufferStream : Stream
{
- // MessagePack Bin32 block
- // https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family
- const int HeaderLength = 5;
-
private byte[] _buffer;
private int _headerStartOffset;
private int _bodyLength;
- public MessagePackBinaryBlockStream(byte[] buffer, int offset)
+ public MessagePackBufferStream(byte[] buffer, int offset)
{
_buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
_headerStartOffset = offset;
_bodyLength = 0;
-
- // Leave space for header
- MessagePackBinary.EnsureCapacity(ref _buffer, _headerStartOffset, HeaderLength);
}
public byte[] Buffer => _buffer;
@@ -38,8 +29,8 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
public override bool CanSeek => false;
public override bool CanWrite => true;
- // Length is the complete number of bytes being output, including header
- public override long Length => _bodyLength + HeaderLength;
+ // Length is the complete number of bytes being output
+ public override long Length => _bodyLength;
// Position is the index into the writable body (i.e., so position zero
// is the first byte you can actually write a value to)
@@ -65,24 +56,10 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
public override void Write(byte[] src, int srcOffset, int count)
{
- var outputOffset = _headerStartOffset + HeaderLength + _bodyLength;
+ var outputOffset = _headerStartOffset + _bodyLength;
MessagePackBinary.EnsureCapacity(ref _buffer, outputOffset, count);
System.Buffer.BlockCopy(src, srcOffset, _buffer, outputOffset, count);
_bodyLength += count;
}
-
- public override void Close()
- {
- // Write the header into the space we reserved at the beginning
- // This format matches the MessagePack spec
- unchecked
- {
- _buffer[_headerStartOffset] = MessagePackCode.Bin32;
- _buffer[_headerStartOffset + 1] = (byte)(_bodyLength >> 24);
- _buffer[_headerStartOffset + 2] = (byte)(_bodyLength >> 16);
- _buffer[_headerStartOffset + 3] = (byte)(_bodyLength >> 8);
- _buffer[_headerStartOffset + 4] = (byte)(_bodyLength);
- }
- }
}
}
diff --git a/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RemoteRenderer.cs b/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RemoteRenderer.cs
index c50152685f..56bbfaea61 100644
--- a/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RemoteRenderer.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RemoteRenderer.cs
@@ -3,8 +3,10 @@
using System;
using System.Threading.Tasks;
+using MessagePack;
using Microsoft.AspNetCore.Blazor.Components;
using Microsoft.AspNetCore.Blazor.Rendering;
+using Microsoft.AspNetCore.Blazor.Server.Circuits;
using Microsoft.AspNetCore.SignalR;
using Microsoft.JSInterop;
@@ -87,7 +89,15 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
///
protected override void UpdateDisplay(in RenderBatch batch)
{
- var task = _client.SendAsync("JS.RenderBatch", _id, batch);
+ // Send the render batch to the client
+ // Note that we have to capture the data as a byte[] synchronously here, because
+ // SignalR's SendAsync can wait an arbitrary duration before serializing the params.
+ // The RenderBatch buffer will get reused by subsequent renders, so we need to
+ // snapshot its contents now.
+ // TODO: Consider using some kind of array pool instead of allocating a new
+ // buffer on every render.
+ var batchBytes = MessagePackSerializer.Serialize(batch, RenderBatchFormatterResolver.Instance);
+ var task = _client.SendAsync("JS.RenderBatch", _id, batchBytes);
CaptureAsyncExceptions(task);
}
diff --git a/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchFormatterResolver.cs b/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchFormatterResolver.cs
index c4ad0f1c6d..efbf1cd92e 100644
--- a/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchFormatterResolver.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchFormatterResolver.cs
@@ -5,7 +5,6 @@ using MessagePack;
using MessagePack.Formatters;
using Microsoft.AspNetCore.Blazor.Rendering;
using System;
-using System.IO;
namespace Microsoft.AspNetCore.Blazor.Server.Circuits
{
@@ -16,6 +15,8 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
///
internal class RenderBatchFormatterResolver : IFormatterResolver
{
+ public static readonly RenderBatchFormatterResolver Instance = new RenderBatchFormatterResolver();
+
public IMessagePackFormatter GetFormatter()
=> typeof(T) == typeof(RenderBatch) ? (IMessagePackFormatter)RenderBatchFormatter.Instance : null;
@@ -30,17 +31,17 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
public int Serialize(ref byte[] bytes, int offset, RenderBatch value, IFormatterResolver formatterResolver)
{
// Instead of using MessagePackBinary.WriteBytes, we write into a stream that
- // knows how to format its output as a MessagePack binary block. The benefit
+ // knows how to write the data using MessagePack writer APIs. The benefit
// is that we don't have to allocate a second large buffer to capture the
// RenderBatchWriter output - we can just write directly to the underlying
// output buffer.
- using (var binaryBlockStream = new MessagePackBinaryBlockStream(bytes, offset))
- using (var renderBatchWriter = new RenderBatchWriter(binaryBlockStream, leaveOpen: false))
+ using (var bufferStream = new MessagePackBufferStream(bytes, offset))
+ using (var renderBatchWriter = new RenderBatchWriter(bufferStream, leaveOpen: false))
{
renderBatchWriter.Write(value);
- bytes = binaryBlockStream.Buffer; // In case the buffer was expanded
- return (int)binaryBlockStream.Length;
+ bytes = bufferStream.Buffer; // In case the buffer was expanded
+ return (int)bufferStream.Length;
}
}
}
diff --git a/src/Microsoft.AspNetCore.Blazor.Server/DependencyInjection/ServerSideBlazorServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Blazor.Server/DependencyInjection/ServerSideBlazorServiceCollectionExtensions.cs
index bb0b32b5f0..de03080593 100644
--- a/src/Microsoft.AspNetCore.Blazor.Server/DependencyInjection/ServerSideBlazorServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Server/DependencyInjection/ServerSideBlazorServiceCollectionExtensions.cs
@@ -126,10 +126,7 @@ namespace Microsoft.Extensions.DependencyInjection
// method on ISignalRServerBuilder so the developer always has to chain it onto
// their own AddSignalR call. For now we're keeping it like this because it's
// simpler for developers in common cases.
- services.AddSignalR().AddMessagePackProtocol(options =>
- {
- options.FormatterResolvers.Insert(0, new RenderBatchFormatterResolver());
- });
+ services.AddSignalR().AddMessagePackProtocol();
}
}
}
diff --git a/test/Microsoft.AspnetCore.Blazor.Server.Test/Circuits/MessagePackBinaryBlockStreamTest.cs b/test/Microsoft.AspnetCore.Blazor.Server.Test/Circuits/MessagePackBufferStreamTest.cs
similarity index 56%
rename from test/Microsoft.AspnetCore.Blazor.Server.Test/Circuits/MessagePackBinaryBlockStreamTest.cs
rename to test/Microsoft.AspnetCore.Blazor.Server.Test/Circuits/MessagePackBufferStreamTest.cs
index 3e6dd725cc..234ab07e42 100644
--- a/test/Microsoft.AspnetCore.Blazor.Server.Test/Circuits/MessagePackBinaryBlockStreamTest.cs
+++ b/test/Microsoft.AspnetCore.Blazor.Server.Test/Circuits/MessagePackBufferStreamTest.cs
@@ -1,41 +1,25 @@
// 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 MessagePack;
using Microsoft.AspNetCore.Blazor.Server.Circuits;
using System;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Server
{
- public class MessagePackBinaryBlockStreamTest
+ public class MessagePackBufferStreamTest
{
[Fact]
public void NullBuffer_Throws()
{
var ex = Assert.Throws(() =>
{
- new MessagePackBinaryBlockStream(null, 0);
+ new MessagePackBufferStream(null, 0);
});
Assert.Equal("buffer", ex.ParamName);
}
- [Fact]
- public void WithNoWrites_JustOutputsHeader()
- {
- // Arrange
- var buffer = new byte[100];
- var offset = 58; // Arbitrary
-
- // Act
- new MessagePackBinaryBlockStream(buffer, offset).Dispose();
-
- // Assert
- Assert.Equal(MessagePackCode.Bin32, buffer[offset]);
- Assert.Equal(0, ReadBigEndianInt32(buffer, offset + 1));
- }
-
[Fact]
public void WithWrites_WritesToUnderlyingBuffer()
{
@@ -44,32 +28,30 @@ namespace Microsoft.AspNetCore.Blazor.Server
var offset = 58; // Arbitrary
// Act/Assert
- using (var stream = new MessagePackBinaryBlockStream(buffer, offset))
+ using (var stream = new MessagePackBufferStream(buffer, offset))
{
stream.Write(new byte[] { 10, 20, 30, 40 }, 1, 2); // Write 2 bytes
stream.Write(new byte[] { 101 }, 0, 1); // Write another 1 byte
stream.Close();
- Assert.Equal(MessagePackCode.Bin32, buffer[offset]);
- Assert.Equal(3, ReadBigEndianInt32(buffer, offset + 1));
- Assert.Equal(20, buffer[offset + 5]);
- Assert.Equal(30, buffer[offset + 6]);
- Assert.Equal(101, buffer[offset + 7]);
+ Assert.Equal(20, buffer[offset]);
+ Assert.Equal(30, buffer[offset + 1]);
+ Assert.Equal(101, buffer[offset + 2]);
}
}
[Fact]
- public void LengthIncludesHeaderButPositionDoesNot()
+ public void LengthAndPositionAreEquivalent()
{
// Arrange
var buffer = new byte[20];
var offset = 3;
// Act/Assert
- using (var stream = new MessagePackBinaryBlockStream(buffer, offset))
+ using (var stream = new MessagePackBufferStream(buffer, offset))
{
stream.Write(new byte[] { 0x01, 0x02 }, 0, 2);
- Assert.Equal(7, stream.Length);
+ Assert.Equal(2, stream.Length);
Assert.Equal(2, stream.Position);
}
}
@@ -78,15 +60,15 @@ namespace Microsoft.AspNetCore.Blazor.Server
public void WithWrites_ExpandsBufferWhenNeeded()
{
// Arrange
- var origBuffer = new byte[15];
+ var origBuffer = new byte[10];
var offset = 6;
origBuffer[0] = 123; // So we can check it was retained during expansion
// Act/Assert
- using (var stream = new MessagePackBinaryBlockStream(origBuffer, offset))
+ using (var stream = new MessagePackBufferStream(origBuffer, offset))
{
- // We can fit the 6-byte offset plus 5-byte header plus 3 written bytes
- // into the original 15-byte buffer
+ // We can fit the 6-byte offset plus 3 written bytes
+ // into the original 10-byte buffer
stream.Write(new byte[] { 10, 20, 30 }, 0, 3);
Assert.Same(origBuffer, stream.Buffer);
@@ -98,13 +80,11 @@ namespace Microsoft.AspNetCore.Blazor.Server
// Check the expanded buffer has the expected contents
stream.Close();
Assert.Equal(123, stream.Buffer[0]); // Retains other values from original buffer
- Assert.Equal(MessagePackCode.Bin32, stream.Buffer[offset]);
- Assert.Equal(5, ReadBigEndianInt32(stream.Buffer, offset + 1));
- Assert.Equal(10, stream.Buffer[offset + 5]);
- Assert.Equal(20, stream.Buffer[offset + 6]);
- Assert.Equal(30, stream.Buffer[offset + 7]);
- Assert.Equal(40, stream.Buffer[offset + 8]);
- Assert.Equal(50, stream.Buffer[offset + 9]);
+ Assert.Equal(10, stream.Buffer[offset]);
+ Assert.Equal(20, stream.Buffer[offset + 1]);
+ Assert.Equal(30, stream.Buffer[offset + 2]);
+ Assert.Equal(40, stream.Buffer[offset + 3]);
+ Assert.Equal(50, stream.Buffer[offset + 4]);
}
}