diff --git a/samples/HttpSend/Program.cs b/samples/HttpSend/Program.cs
index 9f02f34..fa07f41 100644
--- a/samples/HttpSend/Program.cs
+++ b/samples/HttpSend/Program.cs
@@ -41,7 +41,7 @@ namespace HttpSend
Data = JsonConvert.SerializeObject("hey there!")
};
- var content = new CloudEventHttpContent(cloudEvent, ContentMode.Structured, new JsonEventFormatter());
+ var content = cloudEvent.ToHttpContent(ContentMode.Structured, new JsonEventFormatter());
var httpClient = new HttpClient();
// your application remains in charge of adding any further headers or
diff --git a/src/CloudNative.CloudEvents/Http/CloudEventHttpContent.cs b/src/CloudNative.CloudEvents/Http/CloudEventHttpContent.cs
deleted file mode 100644
index 467a4d0..0000000
--- a/src/CloudNative.CloudEvents/Http/CloudEventHttpContent.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (c) Cloud Native Foundation.
-// Licensed under the Apache 2.0 license.
-// See LICENSE file in the project root for full license information.
-
-using System;
-using System.IO;
-using System.Net;
-using System.Net.Http;
-using System.Net.Mime;
-using System.Threading.Tasks;
-
-namespace CloudNative.CloudEvents.Http
-{
- // TODO: Do we really need to have a subclass here? How about a static factory method instead?
-
- ///
- /// This class is for use with `HttpClient` and constructs content and headers for
- /// a HTTP request from a CloudEvent.
- ///
- public class CloudEventHttpContent : HttpContent
- {
- private readonly InnerByteArrayContent inner;
-
- ///
- /// Constructor
- ///
- /// CloudEvent
- /// Content mode. Structured or binary.
- /// Event formatter
- public CloudEventHttpContent(CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter)
- {
- byte[] content;
- ContentType contentType;
- switch (contentMode)
- {
- case ContentMode.Structured:
- content = formatter.EncodeStructuredModeMessage(cloudEvent, out contentType);
- // This is optional in the specification, but can be useful.
- MapHeaders(cloudEvent, includeDataContentType: true);
- break;
- case ContentMode.Binary:
- content = formatter.EncodeBinaryModeEventData(cloudEvent);
- contentType = MimeUtilities.CreateContentTypeOrNull(cloudEvent.DataContentType);
- MapHeaders(cloudEvent, includeDataContentType: false);
- break;
- default:
- throw new ArgumentException($"Unsupported content mode: {contentMode}");
-
- }
- inner = new InnerByteArrayContent(content);
- if (contentType is object)
- {
- Headers.ContentType = contentType.ToMediaTypeHeaderValue();
- }
- else if (content.Length != 0)
- {
- throw new ArgumentException(Strings.ErrorContentTypeUnspecified, nameof(cloudEvent));
- }
- }
-
- protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) =>
- inner.InnerSerializeToStreamAsync(stream, context);
-
- protected override bool TryComputeLength(out long length) =>
- inner.InnerTryComputeLength(out length);
-
- private void MapHeaders(CloudEvent cloudEvent, bool includeDataContentType)
- {
- Headers.Add(HttpUtilities.SpecVersionHttpHeader, HttpUtilities.EncodeHeaderValue(cloudEvent.SpecVersion.VersionId));
- foreach (var attributeAndValue in cloudEvent.GetPopulatedAttributes())
- {
- CloudEventAttribute attribute = attributeAndValue.Key;
- string headerName = HttpUtilities.HttpHeaderPrefix + attribute.Name;
- object value = attributeAndValue.Value;
-
- // Only map the data content type attribute to a header if we've been asked to
- if (attribute == cloudEvent.SpecVersion.DataContentTypeAttribute && !includeDataContentType)
- {
- continue;
- }
- else
- {
- string headerValue = HttpUtilities.EncodeHeaderValue(attribute.Format(value));
- Headers.Add(headerName, headerValue);
- }
- }
- }
-
- ///
- /// This inner class is required to get around the 'protected'-ness of the
- /// override functions of HttpContent for enabling containment/delegation
- ///
- class InnerByteArrayContent : ByteArrayContent
- {
- public InnerByteArrayContent(byte[] content) : base(content)
- {
- }
-
- public Task InnerSerializeToStreamAsync(Stream stream, TransportContext context)
- {
- return base.SerializeToStreamAsync(stream, context);
- }
-
- public bool InnerTryComputeLength(out long length)
- {
- return base.TryComputeLength(out length);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/CloudNative.CloudEvents/Http/HttpContentExtensions.cs b/src/CloudNative.CloudEvents/Http/HttpContentExtensions.cs
new file mode 100644
index 0000000..8b33ca5
--- /dev/null
+++ b/src/CloudNative.CloudEvents/Http/HttpContentExtensions.cs
@@ -0,0 +1,73 @@
+// Copyright 2021 Cloud Native Foundation.
+// Licensed under the Apache 2.0 license.
+// See LICENSE file in the project root for full license information.
+
+using System;
+using System.Net.Http;
+using System.Net.Mime;
+
+namespace CloudNative.CloudEvents.Http
+{
+ ///
+ /// CloudEvent extension methods related to .
+ ///
+ public static class HttpContentExtensions
+ {
+ ///
+ /// Converts a CloudEvent to .
+ ///
+ /// CloudEvent
+ /// Content mode. Structured or binary.
+ /// Event formatter
+ public static HttpContent ToHttpContent(this CloudEvent cloudEvent, ContentMode contentMode, CloudEventFormatter formatter)
+ {
+ byte[] content;
+ // The content type to include in the ContentType header - may be the data content type, or the formatter's content type.
+ ContentType contentType;
+ switch (contentMode)
+ {
+ case ContentMode.Structured:
+ content = formatter.EncodeStructuredModeMessage(cloudEvent, out contentType);
+ break;
+ case ContentMode.Binary:
+ content = formatter.EncodeBinaryModeEventData(cloudEvent);
+ contentType = MimeUtilities.CreateContentTypeOrNull(cloudEvent.DataContentType);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(contentMode), $"Unsupported content mode: {contentMode}");
+ }
+ var ret = new ByteArrayContent(content);
+ if (contentType is object)
+ {
+ ret.Headers.ContentType = contentType.ToMediaTypeHeaderValue();
+ }
+ else if (content.Length != 0)
+ {
+ throw new ArgumentException(Strings.ErrorContentTypeUnspecified, nameof(cloudEvent));
+ }
+
+ // Map headers in either mode.
+ // Including the headers in structured mode is optional in the spec (as they're already within the body) but
+ // can be useful.
+ ret.Headers.Add(HttpUtilities.SpecVersionHttpHeader, HttpUtilities.EncodeHeaderValue(cloudEvent.SpecVersion.VersionId));
+ foreach (var attributeAndValue in cloudEvent.GetPopulatedAttributes())
+ {
+ CloudEventAttribute attribute = attributeAndValue.Key;
+ string headerName = HttpUtilities.HttpHeaderPrefix + attribute.Name;
+ object value = attributeAndValue.Value;
+
+ // Skip the data content type attribute in binary mode, because it's already in the content type header.
+ if (attribute == cloudEvent.SpecVersion.DataContentTypeAttribute && contentMode == ContentMode.Binary)
+ {
+ continue;
+ }
+ else
+ {
+ string headerValue = HttpUtilities.EncodeHeaderValue(attribute.Format(value));
+ ret.Headers.Add(headerName, headerValue);
+ }
+ }
+ return ret;
+ }
+ }
+}
diff --git a/test/CloudNative.CloudEvents.IntegrationTests/AspNetCore/CloudEventControllerTests.cs b/test/CloudNative.CloudEvents.IntegrationTests/AspNetCore/CloudEventControllerTests.cs
index 7e8336d..ca8df59 100644
--- a/test/CloudNative.CloudEvents.IntegrationTests/AspNetCore/CloudEventControllerTests.cs
+++ b/test/CloudNative.CloudEvents.IntegrationTests/AspNetCore/CloudEventControllerTests.cs
@@ -40,7 +40,7 @@ namespace CloudNative.CloudEvents.IntegrationTests.AspNetCore
[expectedExtensionKey] = expectedExtensionValue
};
- var httpContent = new CloudEventHttpContent(cloudEvent, contentMode, new JsonEventFormatter());
+ var httpContent = cloudEvent.ToHttpContent(contentMode, new JsonEventFormatter());
// Act
var result = await _factory.CreateClient().PostAsync("/api/events/receive", httpContent);
diff --git a/test/CloudNative.CloudEvents.UnitTests/Http/HttpClientExtensionsTest.cs b/test/CloudNative.CloudEvents.UnitTests/Http/HttpClientExtensionsTest.cs
index 7e77aca..b841b9a 100644
--- a/test/CloudNative.CloudEvents.UnitTests/Http/HttpClientExtensionsTest.cs
+++ b/test/CloudNative.CloudEvents.UnitTests/Http/HttpClientExtensionsTest.cs
@@ -95,7 +95,7 @@ namespace CloudNative.CloudEvents.Http.UnitTests
};
string ctx = Guid.NewGuid().ToString();
- var content = new CloudEventHttpContent(cloudEvent, ContentMode.Binary, new JsonEventFormatter());
+ var content = cloudEvent.ToHttpContent(ContentMode.Binary, new JsonEventFormatter());
content.Headers.Add(TestContextHeader, ctx);
PendingRequests.TryAdd(ctx, context =>
@@ -215,7 +215,7 @@ namespace CloudNative.CloudEvents.Http.UnitTests
};
string ctx = Guid.NewGuid().ToString();
- var content = new CloudEventHttpContent(cloudEvent, ContentMode.Structured, new JsonEventFormatter());
+ var content = cloudEvent.ToHttpContent(ContentMode.Structured, new JsonEventFormatter());
content.Headers.Add(TestContextHeader, ctx);
PendingRequests.TryAdd(ctx, context =>
diff --git a/test/CloudNative.CloudEvents.UnitTests/Http/CloudEventHttpContentTest.cs b/test/CloudNative.CloudEvents.UnitTests/Http/HttpContentExtensionsTest.cs
similarity index 63%
rename from test/CloudNative.CloudEvents.UnitTests/Http/CloudEventHttpContentTest.cs
rename to test/CloudNative.CloudEvents.UnitTests/Http/HttpContentExtensionsTest.cs
index 9e4b8b9..8ca606c 100644
--- a/test/CloudNative.CloudEvents.UnitTests/Http/CloudEventHttpContentTest.cs
+++ b/test/CloudNative.CloudEvents.UnitTests/Http/HttpContentExtensionsTest.cs
@@ -6,17 +6,18 @@ using CloudNative.CloudEvents.NewtonsoftJson;
using System;
using System.Net.Http.Headers;
using Xunit;
+using static CloudNative.CloudEvents.UnitTests.TestHelpers;
namespace CloudNative.CloudEvents.Http.UnitTests
{
- public class CloudEventHttpContentTest
+ public class HttpContentExtensionsTest
{
[Fact]
public void ContentType_FromCloudEvent_BinaryMode()
{
- var cloudEvent = CreateEmptyCloudEvent();
+ var cloudEvent = new CloudEvent().PopulateRequiredAttributes();
cloudEvent.DataContentType = "text/plain";
- var content = new CloudEventHttpContent(cloudEvent, ContentMode.Binary, new JsonEventFormatter());
+ var content = cloudEvent.ToHttpContent(ContentMode.Binary, new JsonEventFormatter());
var expectedContentType = new MediaTypeHeaderValue("text/plain");
Assert.Equal(expectedContentType, content.Headers.ContentType);
}
@@ -27,26 +28,18 @@ namespace CloudNative.CloudEvents.Http.UnitTests
[Fact]
public void NoContentType_NoContent()
{
- var cloudEvent = CreateEmptyCloudEvent();
- var content = new CloudEventHttpContent(cloudEvent, ContentMode.Binary, new JsonEventFormatter());
+ var cloudEvent = new CloudEvent().PopulateRequiredAttributes();
+ var content = cloudEvent.ToHttpContent(ContentMode.Binary, new JsonEventFormatter());
Assert.Null(content.Headers.ContentType);
}
[Fact]
public void NoContentType_WithContent()
{
- var cloudEvent = CreateEmptyCloudEvent();
+ var cloudEvent = new CloudEvent().PopulateRequiredAttributes();
cloudEvent.Data = "Some text";
- var exception = Assert.Throws(() => new CloudEventHttpContent(cloudEvent, ContentMode.Binary, new JsonEventFormatter()));
+ var exception = Assert.Throws(() => cloudEvent.ToHttpContent(ContentMode.Binary, new JsonEventFormatter()));
Assert.StartsWith(Strings.ErrorContentTypeUnspecified, exception.Message);
}
-
- private static CloudEvent CreateEmptyCloudEvent() =>
- new CloudEvent
- {
- Type = "type",
- Source = new Uri("https://source"),
- Id = "id"
- };
}
}