Skip to content

Commit f2fbc6f

Browse files
author
Michaël Catanzariti
authored
Merge pull request #1 from dahomey-technologies/AspNetCore
Added Asp.Net core support
2 parents 0c7d70a + 09d56ba commit f2fbc6f

33 files changed

+507
-87
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
3+
using Microsoft.AspNetCore.Mvc.Formatters;
4+
using Microsoft.AspNetCore.Mvc.ModelBinding;
5+
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
6+
using Microsoft.VisualStudio.TestTools.UnitTesting;
7+
using Moq;
8+
using System;
9+
using System.Collections.Generic;
10+
using System.IO;
11+
using System.Threading.Tasks;
12+
13+
namespace Dahomey.Cbor.AspNetCore.Tests
14+
{
15+
[TestClass]
16+
public class CborInputFormatterTests
17+
{
18+
[TestMethod]
19+
public async Task ReadSimpleObject()
20+
{
21+
const string hexBuffer = "AE67426F6F6C65616EF56553427974650D64427974650C65496E7431360E6655496E7431360F65496E743332106655496E7433321165496E743634126655496E7436341366537472696E6766737472696E676653696E676C65FA41A1AE1466446F75626C65FB40363AE147AE147B684461746554696D6574323031342D30322D32315431393A30303A30305A64456E756D6656616C756531";
22+
byte[] buffer = hexBuffer.HexToBytes();
23+
24+
Mock<HttpRequest> httpRequest = new Mock<HttpRequest>(MockBehavior.Strict);
25+
httpRequest.Setup(r => r.ContentType).Returns("application/cbor");
26+
httpRequest.Setup(r => r.ContentLength).Returns(buffer.Length);
27+
httpRequest.Setup(r => r.Body).Returns(new MemoryStream(buffer));
28+
29+
Mock<HttpContext> httpContext = new Mock<HttpContext>(MockBehavior.Strict);
30+
httpContext.Setup(c => c.Request).Returns(httpRequest.Object);
31+
32+
Mock<ModelMetadata> modelMetadata = new Mock<ModelMetadata>(
33+
MockBehavior.Strict,
34+
ModelMetadataIdentity.ForType(typeof(SimpleObject)));
35+
36+
InputFormatterContext context = new InputFormatterContext(
37+
httpContext.Object,
38+
nameof(SimpleObject),
39+
new ModelStateDictionary(),
40+
modelMetadata.Object,
41+
(stream, encoding) => new StreamReader(stream, encoding));
42+
43+
IInputFormatter inputFormatter = new CborInputFormatter(null);
44+
45+
Assert.IsTrue(inputFormatter.CanRead(context));
46+
47+
InputFormatterResult result = await inputFormatter.ReadAsync(context);
48+
49+
Assert.IsFalse(result.HasError);
50+
Assert.IsTrue(result.IsModelSet);
51+
52+
SimpleObject obj = (SimpleObject)result.Model;
53+
54+
Assert.IsNotNull(obj);
55+
Assert.IsTrue(obj.Boolean);
56+
Assert.AreEqual(12, obj.Byte);
57+
Assert.AreEqual(13, obj.SByte);
58+
Assert.AreEqual(14, obj.Int16);
59+
Assert.AreEqual(15, obj.UInt16);
60+
Assert.AreEqual(16, obj.Int32);
61+
Assert.AreEqual(17u, obj.UInt32);
62+
Assert.AreEqual(18, obj.Int64);
63+
Assert.AreEqual(19ul, obj.UInt64);
64+
Assert.AreEqual(20.21f, obj.Single);
65+
Assert.AreEqual(22.23, obj.Double);
66+
Assert.AreEqual("string", obj.String);
67+
Assert.AreEqual(new DateTime(2014, 02, 21, 19, 0, 0, DateTimeKind.Utc), obj.DateTime);
68+
Assert.AreEqual(EnumTest.Value1, obj.Enum);
69+
}
70+
71+
[DataTestMethod]
72+
[DataRow("application/json", "")]
73+
[DataRow("application/cbor", "application/cbor")]
74+
public void GetSupportedContentTypes(string actualContentType, string expectedContentType)
75+
{
76+
IApiRequestFormatMetadataProvider apiRequestFormatMetadataProvider = new CborInputFormatter(null);
77+
IReadOnlyList<string> contentTypes
78+
= apiRequestFormatMetadataProvider.GetSupportedContentTypes(
79+
actualContentType, typeof(object));
80+
81+
if (string.IsNullOrEmpty(expectedContentType))
82+
{
83+
Assert.IsNull(contentTypes);
84+
}
85+
else
86+
{
87+
Assert.AreEqual(1, contentTypes.Count);
88+
Assert.AreEqual(expectedContentType, contentTypes[0]);
89+
}
90+
}
91+
}
92+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc.Formatters;
3+
using Microsoft.AspNetCore.Mvc.ModelBinding;
4+
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
5+
using Microsoft.VisualStudio.TestTools.UnitTesting;
6+
using Moq;
7+
using System;
8+
using System.IO;
9+
using System.Threading.Tasks;
10+
11+
namespace Dahomey.Cbor.AspNetCore.Tests
12+
{
13+
[TestClass]
14+
public class CborOuputFormatterTests
15+
{
16+
[TestMethod]
17+
public async Task WriteSimpleObject()
18+
{
19+
SimpleObject obj = new SimpleObject
20+
{
21+
Boolean = true,
22+
Byte = 12,
23+
SByte = 13,
24+
Int16 = 14,
25+
UInt16 = 15,
26+
Int32 = 16,
27+
UInt32 = 17u,
28+
Int64 = 18,
29+
UInt64 = 19ul,
30+
Single = 20.21f,
31+
Double = 22.23,
32+
String = "string",
33+
DateTime = new DateTime(2014, 02, 21, 19, 0, 0, DateTimeKind.Utc),
34+
Enum = EnumTest.Value1
35+
};
36+
37+
const string hexBuffer = "AE67426F6F6C65616EF56553427974650D64427974650C65496E7431360E6655496E7431360F65496E743332106655496E7433321165496E743634126655496E7436341366537472696E6766737472696E676653696E676C65FA41A1AE1466446F75626C65FB40363AE147AE147B684461746554696D6574323031342D30322D32315431393A30303A30305A64456E756D6656616C756531";
38+
byte[] buffer = hexBuffer.HexToBytes();
39+
MemoryStream body = new MemoryStream(); ;
40+
41+
Mock<HttpResponse> httpResponse = new Mock<HttpResponse>(MockBehavior.Strict);
42+
httpResponse.SetupSet(r => r.ContentType = "application/cbor");
43+
httpResponse.Setup(r => r.Body).Returns(body);
44+
45+
Mock<HttpContext> httpContext = new Mock<HttpContext>(MockBehavior.Strict);
46+
httpContext.Setup(c => c.Response).Returns(httpResponse.Object);
47+
48+
Mock<ModelMetadata> modelMetadata = new Mock<ModelMetadata>(
49+
MockBehavior.Strict,
50+
ModelMetadataIdentity.ForType(typeof(SimpleObject)));
51+
52+
OutputFormatterWriteContext context = new OutputFormatterWriteContext(
53+
httpContext.Object,
54+
(stream, encoding) => new StreamWriter(stream, encoding),
55+
typeof(SimpleObject),
56+
obj);
57+
58+
IOutputFormatter outputFormatter = new CborOutputFormatter(
59+
new CborOptions
60+
{
61+
EnumFormat = ValueFormat.WriteToString
62+
});
63+
64+
Assert.IsTrue(outputFormatter.CanWriteResult(context));
65+
66+
await outputFormatter.WriteAsync(context);
67+
68+
string actualHexBuffer = BitConverter.ToString(body.ToArray()).Replace("-", "");
69+
Assert.AreEqual(hexBuffer, actualHexBuffer);
70+
}
71+
}
72+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp2.2</TargetFramework>
5+
6+
<IsPackable>false</IsPackable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
11+
<PackageReference Include="Moq" Version="4.12.0" />
12+
<PackageReference Include="MSTest.TestAdapter" Version="1.3.2" />
13+
<PackageReference Include="MSTest.TestFramework" Version="1.3.2" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\Dahomey.Cbor.AspNetCore\Dahomey.Cbor.AspNetCore.csproj" />
18+
</ItemGroup>
19+
20+
</Project>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
3+
namespace Dahomey.Cbor.AspNetCore.Tests
4+
{
5+
public enum EnumTest
6+
{
7+
None = 0,
8+
Value1 = 1,
9+
Value2 = 2,
10+
}
11+
12+
public class SimpleObject
13+
{
14+
public bool Boolean { get; set; }
15+
public sbyte SByte { get; set; }
16+
public byte Byte { get; set; }
17+
public ushort Int16 { get; set; }
18+
public short UInt16 { get; set; }
19+
public int Int32 { get; set; }
20+
public uint UInt32 { get; set; }
21+
public long Int64 { get; set; }
22+
public ulong UInt64 { get; set; }
23+
public string String { get; set; }
24+
public float Single { get; set; }
25+
public double Double { get; set; }
26+
public DateTime DateTime { get; set; }
27+
public EnumTest Enum { get; set; }
28+
}
29+
30+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Linq;
2+
3+
namespace System
4+
{
5+
public static class StringExtensions
6+
{
7+
public static byte[] HexToBytes(this string hexBuffer)
8+
{
9+
return hexBuffer
10+
.Select((c, i) => new { c, i })
11+
.GroupBy(a => a.i / 2)
12+
.Select(grp => new string(grp.Select(g => g.c).ToArray()))
13+
.Select(value => Convert.ToByte(value, 16)).ToArray();
14+
}
15+
}
16+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using Dahomey.Cbor.Serialization;
2+
using Dahomey.Cbor.Serialization.Converters;
3+
using Microsoft.AspNetCore.Mvc.Formatters;
4+
using System;
5+
using System.Buffers;
6+
using System.IO;
7+
using System.Threading.Tasks;
8+
9+
namespace Dahomey.Cbor.AspNetCore
10+
{
11+
public class CborInputFormatter : InputFormatter
12+
{
13+
private readonly static Task<InputFormatterResult> failureTask = Task.FromResult(InputFormatterResult.Failure());
14+
private readonly static Task<InputFormatterResult> noValueTask = Task.FromResult(InputFormatterResult.NoValue());
15+
16+
private readonly CborOptions _cborOptions;
17+
18+
public CborInputFormatter(CborOptions cborOptions)
19+
{
20+
_cborOptions = cborOptions;
21+
22+
SupportedMediaTypes.Add("application/cbor");
23+
}
24+
25+
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
26+
{
27+
try
28+
{
29+
ValueTask<IMemoryOwner<byte>> task = ReadAsync(context.HttpContext.Request.Body);
30+
if (task.IsCompletedSuccessfully)
31+
{
32+
using (task.Result)
33+
{
34+
object model = Deserialize(context.ModelType, task.Result.Memory.Span);
35+
return Task.FromResult(InputFormatterResult.Success(model));
36+
}
37+
}
38+
39+
return FinishReadRequestBodyAsync(context.ModelType, task);
40+
}
41+
catch (Exception ex)
42+
{
43+
context.ModelState.AddModelError("CBOR", ex.Message);
44+
return failureTask;
45+
}
46+
47+
async Task<InputFormatterResult> FinishReadRequestBodyAsync(Type objectType, ValueTask<IMemoryOwner<byte>> task)
48+
{
49+
await task;
50+
using (task.Result)
51+
{
52+
object model = Deserialize(objectType, task.Result.Memory.Span);
53+
return InputFormatterResult.Success(model);
54+
}
55+
}
56+
}
57+
58+
private object Deserialize(Type objectType, ReadOnlySpan<byte> buffer)
59+
{
60+
CborReader reader = new CborReader(buffer, _cborOptions);
61+
ICborConverter cborConverter = CborConverter.Lookup(objectType);
62+
return cborConverter.Read(ref reader);
63+
64+
}
65+
66+
private async ValueTask<IMemoryOwner<byte>> ReadAsync(Stream stream)
67+
{
68+
var totalSize = 0;
69+
IMemoryOwner<byte> buffer = MemoryPool<byte>.Shared.Rent();
70+
int read;
71+
while ((read = await stream.ReadAsync(buffer.Memory)) > 0)
72+
{
73+
if (totalSize + read == buffer.Memory.Length)
74+
{
75+
IMemoryOwner<byte> backup = buffer;
76+
buffer = MemoryPool<byte>.Shared.Rent(backup.Memory.Length * 2);
77+
backup.Memory.Span.CopyTo(buffer.Memory.Span);
78+
backup.Dispose();
79+
}
80+
81+
totalSize += read;
82+
}
83+
84+
return buffer;
85+
}
86+
}
87+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using System;
4+
5+
namespace Dahomey.Cbor.AspNetCore
6+
{
7+
public static class CborMvcCoreBuilderExtensions
8+
{
9+
public static IMvcBuilder AddDahomeyCbor(this IMvcBuilder builder, CborOptions cborOptions = null)
10+
{
11+
if (builder == null)
12+
{
13+
throw new ArgumentNullException(nameof(builder));
14+
}
15+
16+
builder.Services.Configure<MvcOptions>(config =>
17+
{
18+
config.InputFormatters.Add(new CborInputFormatter(cborOptions));
19+
config.OutputFormatters.Add(new CborOutputFormatter(cborOptions));
20+
});
21+
22+
return builder;
23+
}
24+
}
25+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Dahomey.Cbor.Serialization;
2+
using Dahomey.Cbor.Serialization.Converters;
3+
using Dahomey.Cbor.Util;
4+
using Microsoft.AspNetCore.Mvc.Formatters;
5+
using Microsoft.Net.Http.Headers;
6+
using System.Threading.Tasks;
7+
8+
namespace Dahomey.Cbor.AspNetCore
9+
{
10+
public class CborOutputFormatter : OutputFormatter
11+
{
12+
private readonly CborOptions _cborOptions;
13+
14+
public CborOutputFormatter(CborOptions cborOptions)
15+
{
16+
_cborOptions = cborOptions;
17+
18+
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/cbor"));
19+
}
20+
21+
public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
22+
{
23+
using (ByteBufferWriter bufferWriter = new ByteBufferWriter())
24+
{
25+
CborWriter writer = new CborWriter(bufferWriter, _cborOptions);
26+
ICborConverter cborConverter = CborConverter.Lookup(context.Object.GetType());
27+
cborConverter.Write(ref writer, context.Object);
28+
return bufferWriter.CopyToAsync(context.HttpContext.Response.Body);
29+
}
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)