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 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
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,8 @@
using System.Text.Json;

namespace JustSaying.Sample.Restaurant.OrderingApi;

public class MessagingJsonSerializerOptions
{
public JsonSerializerOptions SerializerOptions { get; } = new();
}
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"
}

###
17 changes: 17 additions & 0 deletions samples/src/JustSaying.Sample.Restaurant.OrderingApi/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
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 +25,20 @@
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<MessagingJsonSerializerOptions>(cfg =>
{
cfg.SerializerOptions.TypeInfoResolverChain.Insert(0, ApplicationJsonContext.Default);
});

builder.Services.AddSingleton<IMessageSerializationFactory>(sp =>
new SystemTextJsonSerializationFactory(sp.GetRequiredService<IOptions<MessagingJsonSerializerOptions>>().Value.SerializerOptions));

#pragma warning disable IL2026 // We provide SystemTextJsonSerializationFactory, which is AOT safe
builder.Services.AddJustSaying(config =>
{
config.Client(x =>
Expand Down Expand Up @@ -65,6 +81,7 @@
x.WithTopic<OrderOnItsWayEvent>();
});
});
#pragma warning restore IL2026

// Added a message handler for message type for 'OrderReadyEvent' on topic 'orderreadyevent' and queue 'orderreadyevent'
builder.Services.AddJustSayingHandler<OrderReadyEvent, OrderReadyEventHandler>();
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 @@ -15,6 +15,8 @@ namespace Microsoft.Extensions.DependencyInjection;
[EditorBrowsable(EditorBrowsableState.Never)]
public static class IServiceCollectionExtensions
{
private const string AttributeRequiresUnreferencedCodeMessage = "The AWSSDK.Extensions.NETCore.Setup package has not been updated to support Native AOT compilations";

/// <summary>
/// Adds JustSaying services to the service collection using AWS configuration.
/// </summary>
Expand All @@ -27,6 +29,9 @@ public static class IServiceCollectionExtensions
/// <exception cref="ArgumentNullException">
/// <paramref name="services"/>, <paramref name="configuration"/> or <paramref name="builderConfig"/> is <see langword="null"/>.
/// </exception>
#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(AttributeRequiresUnreferencedCodeMessage)]
#endif
public static void AddJustSayingWithAwsConfig(this IServiceCollection services, IConfiguration configuration, Action<MessagingBusBuilder> builderConfig)
{
if (services is null)
Expand Down Expand Up @@ -59,6 +64,9 @@ public static void AddJustSayingWithAwsConfig(this IServiceCollection services,
/// <exception cref="ArgumentNullException">
/// <paramref name="services"/>, <paramref name="configuration"/> or <paramref name="builderConfig"/> is <see langword="null"/>.
/// </exception>
#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(AttributeRequiresUnreferencedCodeMessage)]
#endif
public static void AddJustSayingWithAwsConfig(this IServiceCollection services, IConfiguration configuration, Action<MessagingBusBuilder, IServiceProvider> builderConfig)
{
if (services is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
[EditorBrowsable(EditorBrowsableState.Never)]
public static class IServiceCollectionExtensions
{
private const string UnreferencedCodeMessage = "The default IMessageSerializationFactory implementation requires unreferenced code.";

/// <summary>
/// Adds JustSaying services to the service collection.
/// </summary>
Expand All @@ -31,6 +33,9 @@
/// <exception cref="ArgumentNullException">
/// <paramref name="services"/> is <see langword="null"/>.
/// </exception>
#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(UnreferencedCodeMessage)]
#endif
public static IServiceCollection AddJustSaying(this IServiceCollection services)
{
if (services == null)
Expand All @@ -52,6 +57,9 @@
/// <exception cref="ArgumentNullException">
/// <paramref name="services"/> or <paramref name="region"/> is <see langword="null"/>.
/// </exception>
#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(UnreferencedCodeMessage)]
#endif
public static IServiceCollection AddJustSaying(this IServiceCollection services, string region)
{
if (services == null)
Expand All @@ -61,7 +69,7 @@

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));

Check warning on line 72 in src/JustSaying.Extensions.DependencyInjection.Microsoft/IServiceCollectionExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/JustSaying.Extensions.DependencyInjection.Microsoft/IServiceCollectionExtensions.cs#L72

Added line #L72 was not covered by tests
}

return services.AddJustSaying(
Expand All @@ -80,6 +88,9 @@
/// <exception cref="ArgumentNullException">
/// <paramref name="services"/> or <paramref name="configure"/> is <see langword="null"/>.
/// </exception>
#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(UnreferencedCodeMessage)]
#endif
public static IServiceCollection AddJustSaying(this IServiceCollection services, Action<MessagingBusBuilder> configure)
{
if (services == null)
Expand All @@ -106,6 +117,9 @@
/// <exception cref="ArgumentNullException">
/// <paramref name="services"/> or <paramref name="configure"/> is <see langword="null"/>.
/// </exception>
#if NET8_0_OR_GREATER
[RequiresUnreferencedCode(UnreferencedCodeMessage)]
#endif
public static IServiceCollection AddJustSaying(this IServiceCollection services, Action<MessagingBusBuilder, IServiceProvider> configure)
{
if (services == null)
Expand Down Expand Up @@ -136,6 +150,7 @@
services.TryAddSingleton<IMessageContextReader>(serviceProvider => serviceProvider.GetRequiredService<MessageContextAccessor>());

services.TryAddSingleton<IMessageSerializationFactory, NewtonsoftSerializationFactory>();

services.TryAddSingleton<IMessageSubjectProvider, GenericMessageSubjectProvider>();
services.TryAddSingleton<IVerifyAmazonQueues, AmazonQueueCreator>();
services.TryAddSingleton<IMessageSerializationRegister>(
Expand Down Expand Up @@ -199,7 +214,11 @@
/// <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
}
}
16 changes: 13 additions & 3 deletions src/JustSaying/AwsTools/QueueCreation/RedrivePolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace JustSaying.AwsTools.QueueCreation;

[Obsolete("This API is not used by the public API, and will be made internal in a future version.")]
public class RedrivePolicy
{
[JsonProperty("maxReceiveCount")]
Expand All @@ -13,6 +14,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 +27,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 generic SystemTextJsonSerializer<T>.";
internal const string SerializationDynamicCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed. Use the generic SystemTextJsonSerializer<T>.";
}
25 changes: 25 additions & 0 deletions src/JustSaying/Extensions/JsonElementExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#nullable enable
using System.Text.Json;

namespace JustSaying.Extensions;

internal static class JsonElementExtensions
{
#if NET8_0_OR_GREATER
public static bool TryGetStringProperty(this JsonElement element, string key, [NotNullWhen(true)] out string? value)
#else
public static bool TryGetStringProperty(this JsonElement element, string key, out string? value)
#endif
{
value = null;

Check warning on line 14 in src/JustSaying/Extensions/JsonElementExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/JustSaying/Extensions/JsonElementExtensions.cs#L14

Added line #L14 was not covered by tests
if (element.TryGetProperty(key, out var property)
&& property.ValueKind is JsonValueKind.String or JsonValueKind.Null
&& property.GetString() is {} propertyValue)

Check warning on line 17 in src/JustSaying/Extensions/JsonElementExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/JustSaying/Extensions/JsonElementExtensions.cs#L16-L17

Added lines #L16 - L17 were not covered by tests
{
value = propertyValue;
return true;

Check warning on line 20 in src/JustSaying/Extensions/JsonElementExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/JustSaying/Extensions/JsonElementExtensions.cs#L19-L20

Added lines #L19 - L20 were not covered by tests
}

return false;

Check warning on line 23 in src/JustSaying/Extensions/JsonElementExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/JustSaying/Extensions/JsonElementExtensions.cs#L23

Added line #L23 was not covered by tests
}
}
18 changes: 18 additions & 0 deletions src/JustSaying/Extensions/JsonSerializerOptionsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#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)
{
// This throws a NotSupportedException if type information is not available for a given type,
// or an ArgumentException if the type is not valid for serialization (void, pointer types, and similar).
// We don't guard this to allow the default exception to be propagated.
var typeInfo = options.GetTypeInfo(typeof(T));
return (JsonTypeInfo<T>)typeInfo;
}
}
#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
};
}
}
}
Loading
Loading