Skip to content
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

Add .NET 7, 8, trimming and AOT support #273

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
7 changes: 4 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Build

on:
workflow_dispatch:
push:
branches:
- main
Expand All @@ -15,15 +16,15 @@ jobs:

steps:
- name: Check out our repo
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: true

# Build with .NET 6.0 SDK
- name: Setup .NET 6.0
- name: Setup .NET 8.0
uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
dotnet-version: 8.0.x

- name: Build
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ jobs:

steps:
- name: Check out our repo
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: true

# Build with .NET 6.0 SDK
- name: Setup .NET 6.0
uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
dotnet-version: 8.0.x

- name: Build
run: |
Expand Down
34 changes: 34 additions & 0 deletions CloudEvents.sln
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "xml", "xml", "{4012C753-68D
conformance\format\xml\valid-events.xml = conformance\format\xml\valid-events.xml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpSendJson", "samples\HttpSendJson\HttpSendJson.csproj", "{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5AD5E051-9A8E-46D9-B0C5-8933718C6D1F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudNative.CloudEvents.MinApiSample", "samples\CloudNative.CloudEvents.MinApiSample\CloudNative.CloudEvents.MinApiSample.csproj", "{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -238,15 +244,43 @@ Global
{9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Release|x64.Build.0 = Release|Any CPU
{9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Release|x86.ActiveCfg = Release|Any CPU
{9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Release|x86.Build.0 = Release|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|x64.ActiveCfg = Debug|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|x64.Build.0 = Debug|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|x86.ActiveCfg = Debug|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|x86.Build.0 = Debug|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|Any CPU.Build.0 = Release|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|x64.ActiveCfg = Release|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|x64.Build.0 = Release|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|x86.ActiveCfg = Release|Any CPU
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|x86.Build.0 = Release|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|x64.ActiveCfg = Debug|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|x64.Build.0 = Debug|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|x86.ActiveCfg = Debug|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|x86.Build.0 = Debug|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|Any CPU.Build.0 = Release|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|x64.ActiveCfg = Release|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|x64.Build.0 = Release|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|x86.ActiveCfg = Release|Any CPU
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F1B9B769-DB6B-481F-905C-24FE3B12E00E} = {5AD5E051-9A8E-46D9-B0C5-8933718C6D1F}
{9760D744-D1BF-40E3-BD6F-7F639BFB9188} = {5AD5E051-9A8E-46D9-B0C5-8933718C6D1F}
{A5906FBA-D73A-4A09-8539-CB10D7B586AE} = {8CCC98B3-1776-49FF-96D6-947A9E5DFB0A}
{D8055631-E6BB-4CD2-8162-F674D6D30E76} = {A5906FBA-D73A-4A09-8539-CB10D7B586AE}
{119AD438-878B-4383-BC9F-779F1605E711} = {A5906FBA-D73A-4A09-8539-CB10D7B586AE}
{4012C753-68DE-4737-936F-F5DBC485C51B} = {A5906FBA-D73A-4A09-8539-CB10D7B586AE}
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8} = {5AD5E051-9A8E-46D9-B0C5-8933718C6D1F}
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF} = {5AD5E051-9A8E-46D9-B0C5-8933718C6D1F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F77A454C-CC17-4AD6-823A-64E1A94FDA0A}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\CloudNative.CloudEvents.AspNetCore\CloudNative.CloudEvents.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\CloudNative.CloudEvents\CloudNative.CloudEvents.csproj" />
<ProjectReference Include="..\..\src\CloudNative.CloudEvents.SystemTextJson\CloudNative.CloudEvents.SystemTextJson.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<SelfContained>true</SelfContained>
<PublishAot>true</PublishAot>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
</PropertyGroup>
</Project>
85 changes: 85 additions & 0 deletions samples/CloudNative.CloudEvents.MinApiSample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) Cloud Native Foundation.
// Licensed under the Apache 2.0 license.
// See LICENSE file in the project root for full license information.

using CloudNative.CloudEvents;
using CloudNative.CloudEvents.Http;
using CloudNative.CloudEvents.SystemTextJson;
using CloudNative.CloudEvents.AspNetCore;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Text;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var formatter = new JsonEventFormatter<Message>(MyJsonContext.Default);

app.MapPost("/api/events/receive/", async (HttpRequest request) =>
{
var cloudEvent = await request.ToCloudEventAsync(formatter);
using var ms = new MemoryStream();
using var writer = new Utf8JsonWriter(ms, new() { Indented = true });
writer.WriteStartObject();
foreach (var (attribute, value) in cloudEvent.GetPopulatedAttributes())
writer.WriteString(attribute.Name, attribute.Format(value));
writer.WriteEndObject();
await writer.FlushAsync();
var attributeMap = Encoding.UTF8.GetString(ms.ToArray());
return Results.Text($"Received event with ID {cloudEvent.Id}, attributes: {attributeMap}");
});

app.MapPost("/api/events/receive2/", (Event e) => Results.Json(e.CloudEvent.Data, MyJsonContext.Default));

app.MapPost("/api/events/receive3/", (Message message) => Results.Json(message, MyJsonContext.Default));

app.MapGet("/api/events/generate/", () =>
{
var evt = new CloudEvent
{
Type = "CloudNative.CloudEvents.MinApiSample",
Source = new Uri("https://github.com/cloudevents/sdk-csharp"),
Time = DateTimeOffset.Now,
DataContentType = "application/json",
Id = Guid.NewGuid().ToString(),
Data = new Message("C#", Environment.Version.ToString())
};
// Format the event as the body of the response. This is UTF-8 JSON because of
// the CloudEventFormatter we're using, but EncodeStructuredModeMessage always
// returns binary data. We could return the data directly, but for debugging
// purposes it's useful to have the JSON string.
var bytes = formatter.EncodeStructuredModeMessage(evt, out var contentType);
string json = Encoding.UTF8.GetString(bytes.Span);
// Specify the content type of the response: this is what makes it a CloudEvent.
// (In "binary mode", the content type is the content type of the data, and headers
// indicate that it's a CloudEvent.)
return Results.Content(json, contentType.MediaType, Encoding.UTF8);
});

app.Run();

[JsonSerializable(typeof(Message))]
internal partial class MyJsonContext : JsonSerializerContext { }

public class Event
{
private readonly static JsonEventFormatter formatter = new JsonEventFormatter<Message>(MyJsonContext.Default);
// required for receive2
public static async ValueTask<Event?> BindAsync(HttpContext context)
{
var cloudEvent = await context.Request.ToCloudEventAsync(formatter);
return new Event { CloudEvent = cloudEvent };
}
public required CloudEvent CloudEvent { get; init; }
}

record class Message(string Language, string EnvironmentVersion)
{
private readonly static JsonEventFormatter formatter = new JsonEventFormatter<Message>(MyJsonContext.Default);
// required for receive3
public static async ValueTask<Message?> BindAsync(HttpContext context)
{
var cloudEvent = await context.Request.ToCloudEventAsync(formatter);
return cloudEvent.Data is Message message ? message : null;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "api/events/generate",
"applicationUrl": "http://localhost:5002",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions samples/CloudNative.CloudEvents.MinApiSample/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
1 change: 1 addition & 0 deletions samples/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@

<!-- Never pack any sample projects -->
<IsPackable>False</IsPackable>
<LangVersion>12.0</LangVersion>
</PropertyGroup>
</Project>
19 changes: 19 additions & 0 deletions samples/HttpSendJson/HttpSendJson.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="docopt.net" Version="0.8.1" />
<ProjectReference Include="..\..\src\CloudNative.CloudEvents\CloudNative.CloudEvents.csproj" />
<ProjectReference Include="..\..\src\CloudNative.CloudEvents.SystemTextJson\CloudNative.CloudEvents.SystemTextJson.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<SelfContained>true</SelfContained>
<PublishAot>true</PublishAot>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
</PropertyGroup>
</Project>
70 changes: 70 additions & 0 deletions samples/HttpSendJson/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Cloud Native Foundation.
// Licensed under the Apache 2.0 license.
// See LICENSE file in the project root for full license information.

using CloudNative.CloudEvents;
using CloudNative.CloudEvents.Http;
using CloudNative.CloudEvents.SystemTextJson;
using DocoptNet;
using System.Net.Mime;
using static System.Console;

// This application uses the docopt.net library for parsing the command
// line and calling the application code.
ProgramArguments programArguments = new();
var result = await ProgramArguments.CreateParserWithVersion()
.Parse(args)
.Match(RunAsync,
result => { WriteLine(result.Help); return Task.FromResult(1); },
result => { WriteLine(result.Version); return Task.FromResult(0); },
result => { Error.WriteLine(result.Usage); return Task.FromResult(1); });
return result;

static async Task<int> RunAsync(ProgramArguments args)
{
var cloudEvent = new CloudEvent
{
Id = Guid.NewGuid().ToString(),
Type = args.OptType,
Source = new Uri(args.OptSource),
DataContentType = MediaTypeNames.Application.Json,
Data = System.Text.Json.JsonSerializer.Serialize("hey there!", GeneratedJsonContext.Default.String)
};

var content = cloudEvent.ToHttpContent(ContentMode.Structured, new JsonEventFormatter(GeneratedJsonContext.Default));

var httpClient = new HttpClient();
// Your application remains in charge of adding any further headers or
// other information required to authenticate/authorize or otherwise
// dispatch the call at the server.
var result = await httpClient.PostAsync(args.OptUrl, content);

WriteLine(result.StatusCode);
return 0;
}

[System.Text.Json.Serialization.JsonSerializable(typeof(string))]
internal partial class GeneratedJsonContext : System.Text.Json.Serialization.JsonSerializerContext
{
}

[DocoptArguments]
partial class ProgramArguments
{
const string Help = @"HttpSendJson.

Usage:
HttpSendJson --url=URL [--type=TYPE] [--source=SOURCE]
HttpSendJson (-h | --help)
HttpSendJson --version

Options:
--url=URL HTTP(S) address to send the event to.
--type=TYPE CloudEvents 'type' [default: com.example.myevent].
--source=SOURCE CloudEvents 'source' [default: urn:example-com:mysource:abc].
-h --help Show this screen.
--version Show version.
";
public static string Version => $"producer {typeof(ProgramArguments).Assembly.GetName().Version}";
public static IParser<ProgramArguments> CreateParserWithVersion() => CreateParser().WithVersion(Version);
}
24 changes: 22 additions & 2 deletions src/CloudNative.CloudEvents.Amqp/AmqpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using CloudNative.CloudEvents.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Mime;

Expand Down Expand Up @@ -145,7 +146,7 @@ public static CloudEvent ToCloudEvent(
}
}

private static bool HasCloudEventsContentType(Message message, out string? contentType)
private static bool HasCloudEventsContentType(Message message, [NotNullWhen(true)] out string? contentType)
{
contentType = message.Properties.ContentType?.ToString();
return MimeUtilities.IsCloudEventsContentType(contentType);
Expand Down Expand Up @@ -249,4 +250,23 @@ private static ApplicationProperties MapHeaders(CloudEvent cloudEvent, string pr
return applicationProperties;
}
}
}
}

#if NETSTANDARD2_0
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;

/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
}
#endif
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
<Description>AMQP extensions for CloudNative.CloudEvents</Description>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
<PackageTags>cncf;cloudnative;cloudevents;events;amqp</PackageTags>
</PropertyGroup>
Expand Down
Loading