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

Support Native AOT #1348

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 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
6 changes: 6 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,10 @@
<None Include="$(MSBuildThisFileDirectory)$(PackageIcon)" Pack="True" PackagePath="" />
<None Include="$(MSBuildThisFileDirectory)$(PackageReadmeFile)" Pack="True" PackagePath="" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0'))">
<Using Include="System.Diagnostics.CodeAnalysis" />
</ItemGroup>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0'))">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
</Project>
5 changes: 3 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<Project>
<ItemGroup Label="Libraries">
<PackageVersion Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.300" />
<PackageVersion Include="AWSSDK.SimpleNotificationService" Version="3.7.0" />
<PackageVersion Include="AWSSDK.SQS" Version="3.7.0" />
<PackageVersion Include="AWSSDK.SimpleNotificationService" Version="3.7.300" />
<PackageVersion Include="AWSSDK.SQS" Version="3.7.300" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.0" Condition=" '$(TargetFramework)' == 'net461' " />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" Condition=" '$(TargetFramework)' == 'netstandard2.0' " />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" Condition=" '$(TargetFramework)' == 'net8.0' " />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" Condition=" '$(TargetFramework)' == 'net8.0' " />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
<PackageVersion Include="StructureMap" Version="4.6.0" />
<PackageVersion Include="System.Text.Json" Version="4.6.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
using JustSaying.Sample.Restaurant.Models;
using JustSaying.Sample.Restaurant.OrderingApi.Models;

namespace JustSaying.Sample.Restaurant.OrderingApi;

[JsonSerializable(typeof(CustomerOrderModel))]
[JsonSerializable(typeof(OrderPlacedEvent))]
[JsonSerializable(typeof(OrderReadyEvent))]
[JsonSerializable(typeof(OrderDeliveredEvent))]
[JsonSerializable(typeof(OrderOnItsWayEvent))]
public sealed partial class ApplicationJsonContext : JsonSerializerContext;
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
</ItemGroup>

<ItemGroup>
<Content Update="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
<Content Update="appsettings.json" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>

<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@HostAddress = http://localhost:5001

POST {{HostAddress}}/api/orders
Content-Type: application/json
Accept: application/json

{
"description": "A cool description"
}

###
13 changes: 13 additions & 0 deletions samples/src/JustSaying.Sample.Restaurant.OrderingApi/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System.Text.Json;
using JustSaying.Messaging;
using JustSaying.Messaging.MessageSerialization;
using JustSaying.Sample.Restaurant.Models;
using JustSaying.Sample.Restaurant.OrderingApi;
using JustSaying.Sample.Restaurant.OrderingApi.Handlers;
using JustSaying.Sample.Restaurant.OrderingApi.Models;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Serilog;
using Serilog.Events;
Expand All @@ -23,6 +26,16 @@
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
builder.Host.UseSerilog();
builder.Services.ConfigureHttpJsonOptions(cfg =>
{
cfg.SerializerOptions.TypeInfoResolverChain.Insert(0, ApplicationJsonContext.Default);
});

builder.Services.Configure<JustSayingJsonSerializerOptions>(cfg =>
{
cfg.SerializerOptions.TypeInfoResolverChain.Insert(0, ApplicationJsonContext.Default);
});

builder.Services.AddJustSaying(config =>
{
config.Client(x =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"AWSRegion": "eu-west-1",
"AWSServiceUrl": "http://localhost:4100",
"AWSServiceUrl": "http://localhost:4566",
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
using JustSaying.Models;
using JustSaying.Naming;
using Microsoft.Extensions.DependencyInjection.Extensions;
#if NET8_0_OR_GREATER
using System.Text.Json;
using Microsoft.Extensions.Options;
using System.Diagnostics.CodeAnalysis;
#endif

namespace Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -61,7 +66,7 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services,

if (string.IsNullOrWhiteSpace(region))
{
throw new ArgumentException("region must not be null or empty" ,nameof(region));
throw new ArgumentException("region must not be null or empty", nameof(region));
}

return services.AddJustSaying(
Expand Down Expand Up @@ -135,7 +140,11 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services,
services.TryAddSingleton<IMessageContextAccessor>(serviceProvider => serviceProvider.GetRequiredService<MessageContextAccessor>());
services.TryAddSingleton<IMessageContextReader>(serviceProvider => serviceProvider.GetRequiredService<MessageContextAccessor>());

#if NET8_0_OR_GREATER
services.TryAddSingleton<IMessageSerializationFactory>(sp => new TypedSystemTextJsonSerializationFactory(sp.GetRequiredService<IOptions<JustSayingJsonSerializerOptions>>().Value));
#else
services.TryAddSingleton<IMessageSerializationFactory, NewtonsoftSerializationFactory>();
#endif
services.TryAddSingleton<IMessageSubjectProvider, GenericMessageSubjectProvider>();
services.TryAddSingleton<IVerifyAmazonQueues, AmazonQueueCreator>();
services.TryAddSingleton<IMessageSerializationRegister>(
Expand Down Expand Up @@ -199,7 +208,11 @@ public static IServiceCollection AddJustSaying(this IServiceCollection services,
/// <exception cref="ArgumentNullException">
/// <paramref name="services"/> is <see langword="null"/>.
/// </exception>
#if NET8_0_OR_GREATER
public static IServiceCollection AddJustSayingHandler<TMessage, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THandler>(this IServiceCollection services)
#else
public static IServiceCollection AddJustSayingHandler<TMessage, THandler>(this IServiceCollection services)
#endif
where TMessage : Message
where THandler : class, IHandlerAsync<TMessage>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0'))" />
</ItemGroup>
<ItemGroup>
<Using Remove="System.Net.Http" />
</ItemGroup>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)','net8.0'))">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
martincostello marked this conversation as resolved.
Show resolved Hide resolved
</Project>
12 changes: 11 additions & 1 deletion src/JustSaying/AwsTools/MessageHandling/SnsPolicyBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json;
using Amazon;
using JustSaying.Messaging.MessageSerialization;

namespace JustSaying.AwsTools.MessageHandling;

Expand Down Expand Up @@ -42,7 +43,7 @@ internal static string BuildPolicyJson(SnsPolicyDetails policyDetails)
"Sid" : "{{Guid.NewGuid().ToString().Replace("-", "")}}",
"Effect" : "Allow",
"Principal" : {
"AWS" : {{JsonSerializer.Serialize(policyDetails.AccountIds)}}
"AWS" : {{SerializeAccountIds(policyDetails.AccountIds)}}
},
"Action" : "sns:Subscribe",
"Resource" : "{{policyDetails.SourceArn}}"
Expand All @@ -51,4 +52,13 @@ internal static string BuildPolicyJson(SnsPolicyDetails policyDetails)
}
""";
}

private static string SerializeAccountIds(IReadOnlyCollection<string> accountIds)
{
#if NET8_0_OR_GREATER
return JsonSerializer.Serialize(accountIds, JustSayingSerializationContext.Default.IReadOnlyCollectionString);
#else
return JsonSerializer.Serialize(accountIds);
#endif
}
}
15 changes: 12 additions & 3 deletions src/JustSaying/AwsTools/QueueCreation/RedrivePolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public class RedrivePolicy
[JsonPropertyName("deadLetterTargetArn")]
public string DeadLetterQueue { get; set; }

#if NET8_0_OR_GREATER
[System.Text.Json.Serialization.JsonConstructor]
#endif
public RedrivePolicy(int maximumReceives, string deadLetterQueue)
{
MaximumReceives = maximumReceives;
Expand All @@ -23,11 +26,17 @@ protected RedrivePolicy()
{
}

// Cannot use System.Text.Json below as no public parameterless constructor. Change for v7?

public override string ToString()
#if NET8_0_OR_GREATER
=> System.Text.Json.JsonSerializer.Serialize(this, JustSayingSerializationContext.Default.RedrivePolicy);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might be problematic for AoT in case it's a derived type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good point, I think it'll end up serializing the base fields, but none added by the derived type.
At the moment, RedrivePolicy is only used internally, and I can't see a way that an API user could derive it and pass it through to any internal usage, but I could be missing it.

If that is the case, I don't think it would be a bad idea to internal sealed it.

#else
=> JsonConvert.SerializeObject(this);
#endif

public static RedrivePolicy ConvertFromString(string policy)
#if NET8_0_OR_GREATER
=> System.Text.Json.JsonSerializer.Deserialize(policy, JustSayingSerializationContext.Default.RedrivePolicy);
#else
=> JsonConvert.DeserializeObject<RedrivePolicy>(policy);
}
#endif
}
7 changes: 7 additions & 0 deletions src/JustSaying/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace JustSaying;

internal static class Constants
{
internal const string SerializationUnreferencedCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.";
internal const string SerializationDynamicCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext.";
}
26 changes: 26 additions & 0 deletions src/JustSaying/Extensions/JsonSerializerOptionsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#if NET8_0_OR_GREATER
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

namespace JustSaying.Extensions;


internal static class JsonSerializerOptionsExtensions
{
public static JsonTypeInfo<T> GetTypeInfo<T>(this JsonSerializerOptions options)
{
foreach (var info in options.TypeInfoResolverChain)
{
Console.WriteLine(info);
}

var typeInfo = options.GetTypeInfo(typeof(T));
if (typeInfo is not JsonTypeInfo<T> genericTypeInfo)
{
throw new JsonException($"Could not find type info for the specified type {typeof(T).Name}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably be a bit more informative.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ends up being redundant anyway tbh, GetTypeInfo(Type) throws if it can't find the type info anyway.

}

return genericTypeInfo;
}
}
#endif
4 changes: 3 additions & 1 deletion src/JustSaying/Fluent/AccountAddressProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,12 @@ public Uri GetQueueUriByConvention<T>()
/// <returns>The <see cref="Uri"/> for this queue.</returns>
public Uri GetQueueUri(string queueName)
{
#pragma warning disable CS0618 // Type or member is obsolete
var hostname = _regionEndpoint.GetEndpointForService("sqs").Hostname;
#pragma warning restore CS0618 // Type or member is obsolete
return new UriBuilder("https", hostname)
{
Path = $"{_accountId}/{queueName}"
}.Uri;
}
}
}
4 changes: 3 additions & 1 deletion src/JustSaying/Fluent/QueueAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ public static QueueAddress FromArn(string queueArn)
if (!Arn.TryParse(queueArn, out var arn)) throw new ArgumentException("Must be a valid ARN.", nameof(queueArn));
if (!string.Equals(arn.Service, "sqs", StringComparison.OrdinalIgnoreCase)) throw new ArgumentException("Must be an ARN for an SQS queue.", nameof(queueArn));

#pragma warning disable CS0618 // Type or member is obsolete
var hostname = RegionEndpoint.GetBySystemName(arn.Region)
.GetEndpointForService("sqs")
.Hostname;
#pragma warning restore CS0618 // Type or member is obsolete

var queueUrl = new UriBuilder("https", hostname)
{
Expand All @@ -94,4 +96,4 @@ public static QueueAddress FromArn(string queueArn)
RegionName = arn.Region
};
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
using JustSaying.AwsTools;
using JustSaying.Messaging.MessageSerialization;
using JustSaying.Messaging.Monitoring;
Expand Down Expand Up @@ -44,7 +45,22 @@ private object TryResolveService(Type desiredType)
}
else if (desiredType == typeof(IMessageSerializationFactory))
{

#if NET8_0_OR_GREATER
if (RuntimeFeature.IsDynamicCodeSupported)
{
#pragma warning disable IL2026
#pragma warning disable IL3050
return new NewtonsoftSerializationFactory();
#pragma warning restore
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly surprised it isn't smart enough to not warn about this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check, I think I had the check backwards when I added the #pragma warning disable's

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, even with that fixed, it still warns.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found an issue related to this: dotnet/runtime#97273, looks like the supression in runtime checks just didn't make it into .NET 8, not sure if they plan to backport either.

It also looks like there are plans for a wider design proposal to support feature checks suppressing analyzers: dotnet/runtime#96859

else
{
throw new NotSupportedException($"Newtonsoft.Json is not supported when compiled with the 'PublishTrimmed' option. Use {nameof(TypedSystemTextJsonSerializationFactory)} instead.");
}
#else
return new NewtonsoftSerializationFactory();
#endif
}
else if (desiredType == typeof(IMessageSerializationRegister))
{
Expand All @@ -63,4 +79,4 @@ private object TryResolveService(Type desiredType)

return null;
}
}
}
2 changes: 1 addition & 1 deletion src/JustSaying/JustSayingBus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public async Task PublishAsync(
{
if (!_busStarted && _startupTasks.Count > 0)
{
throw new InvalidOperationException("There are pending startup tasks that must be executed by calling StartAsync before messages may be published.");
throw new InvalidOperationException($"There are pending startup tasks that must be executed by calling StartAsync before messages may be published. Bus started {_busStarted}, Count {_startupTasks.Count}");
}

IMessagePublisher publisher = GetPublisherForMessage(message);
Expand Down
12 changes: 12 additions & 0 deletions src/JustSaying/JustSayingSerializationContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#if NET8_0_OR_GREATER
using System.Text.Json.Serialization;
using JustSaying.AwsTools.QueueCreation;
using JustSaying.Messaging.MessageSerialization;

namespace JustSaying;

[JsonSerializable(typeof(SqsMessageEnvelope))]
[JsonSerializable(typeof(IReadOnlyCollection<string>))]
[JsonSerializable(typeof(RedrivePolicy))]
internal sealed partial class JustSayingSerializationContext : JsonSerializerContext;
martincostello marked this conversation as resolved.
Show resolved Hide resolved
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#if NET8_0_OR_GREATER
using System.Text.Json;

namespace JustSaying.Messaging.MessageSerialization;

public class JustSayingJsonSerializerOptions
{
public JsonSerializerOptions SerializerOptions { get; } = new();
martincostello marked this conversation as resolved.
Show resolved Hide resolved
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

public MessageFormatNotSupportedException(string message, Exception innerException) : base(message, innerException)
{
}

Check failure on line 22 in src/JustSaying/Messaging/MessageSerialization/MessageFormatNotSupportedException.cs

View workflow job for this annotation

GitHub Actions / code-ql (csharp)

} expected

Check failure on line 22 in src/JustSaying/Messaging/MessageSerialization/MessageFormatNotSupportedException.cs

View workflow job for this annotation

GitHub Actions / code-ql (csharp)

} expected

Check failure on line 22 in src/JustSaying/Messaging/MessageSerialization/MessageFormatNotSupportedException.cs

View workflow job for this annotation

GitHub Actions / windows-latest

} expected
#if !NET8_0_OR_GREATER

#if !NET8_0_OR_GREATER
protected MessageFormatNotSupportedException(SerializationInfo info, StreamingContext context) : base(info, context)
Expand All @@ -27,3 +28,3 @@
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

namespace JustSaying.Messaging.MessageSerialization;

#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(Constants.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(Constants.SerializationDynamicCodeMessage)]
#endif
public class NewtonsoftSerializationFactory(JsonSerializerSettings settings) : IMessageSerializationFactory
{
private readonly NewtonsoftSerializer _serializer = new(settings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

namespace JustSaying.Messaging.MessageSerialization;

#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(Constants.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(Constants.SerializationDynamicCodeMessage)]
#endif
public class NewtonsoftSerializer : IMessageSerializer
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an assumption at the moment. Newtonsoft.Json isn't currently annotated for AOT/Trimming compatibility.

{
private readonly JsonSerializerSettings _settings;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace JustSaying.Messaging.MessageSerialization;

internal sealed class SqsMessageEnvelope
{
public string Subject { get; set; }

public string Message { get; set; }
}
Loading
Loading