Skip to content

Commit

Permalink
Add NativeAOT compatibility for ASP.Net Core in .NET 8 (#1124)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shane32 authored Feb 19, 2024
1 parent ac38114 commit 010d522
Show file tree
Hide file tree
Showing 24 changed files with 743 additions and 16 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<AnalysisMode>Recommended</AnalysisMode>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<NoWarn>$(NoWarn);IDE0056;IDE0057;ASP0014</NoWarn> <!-- Index/Range operators, UseRouting -->
<NoWarn>$(NoWarn);IDE0056;IDE0057;ASP0014;CA1510;CA1513</NoWarn> <!-- Index/Range operators, UseRouting, throw helpers -->

<AssemblyName>GraphQL.Server.$(MSBuildProjectName)</AssemblyName>
<RootNamespace>GraphQL.Server.$(MSBuildProjectName)</RootNamespace>
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,15 @@ via the `multipart/form-data` content type as attached files. If you wish to al
allow clients to send files as base-64 encoded strings, you can write a custom scalar
better suited to your needs.

### Native AOT support

GraphQL.NET Server fully supports Native AOT publishing with .NET 8.0 and later.
See [ASP.NET Core support for Native AOT](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/native-aot)
for a list of features supported by .NET 8.0. However, GraphQL.NET only provides limited
support for Native AOT publishing due to its extensive use of reflection. Please see
[GraphQL.NET Ahead-of-time compilation](https://github.com/graphql-dotnet/graphql-dotnet?tab=readme-ov-file#ahead-of-time-compilation)
for more information.

## Samples

The following samples are provided to show how to integrate this project with various
Expand Down
3 changes: 2 additions & 1 deletion src/Authorization.AspNetCore/Authorization.AspNetCore.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net8.0</TargetFrameworks>
<Description>Integration of GraphQL.NET validation subsystem into ASP.NET Core</Description>
<PackageTags>GraphQL;authentication;authorization;validation</PackageTags>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// <auto-generated/>
#pragma warning disable
#nullable enable annotations
#if !NET5_0_OR_GREATER

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Diagnostics.CodeAnalysis
{
/// <summary>
/// Specifies the types of members that are dynamically accessed.
///
/// This enumeration has a <see cref="global::System.FlagsAttribute"/> attribute that allows a
/// bitwise combination of its member values.
/// </summary>
[global::System.Flags]
internal enum DynamicallyAccessedMemberTypes
{
/// <summary>
/// Specifies no members.
/// </summary>
None = 0,

/// <summary>
/// Specifies the default, parameterless public constructor.
/// </summary>
PublicParameterlessConstructor = 0x0001,

/// <summary>
/// Specifies all public constructors.
/// </summary>
PublicConstructors = 0x0002 | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor,

/// <summary>
/// Specifies all non-public constructors.
/// </summary>
NonPublicConstructors = 0x0004,

/// <summary>
/// Specifies all public methods.
/// </summary>
PublicMethods = 0x0008,

/// <summary>
/// Specifies all non-public methods.
/// </summary>
NonPublicMethods = 0x0010,

/// <summary>
/// Specifies all public fields.
/// </summary>
PublicFields = 0x0020,

/// <summary>
/// Specifies all non-public fields.
/// </summary>
NonPublicFields = 0x0040,

/// <summary>
/// Specifies all public nested types.
/// </summary>
PublicNestedTypes = 0x0080,

/// <summary>
/// Specifies all non-public nested types.
/// </summary>
NonPublicNestedTypes = 0x0100,

/// <summary>
/// Specifies all public properties.
/// </summary>
PublicProperties = 0x0200,

/// <summary>
/// Specifies all non-public properties.
/// </summary>
NonPublicProperties = 0x0400,

/// <summary>
/// Specifies all public events.
/// </summary>
PublicEvents = 0x0800,

/// <summary>
/// Specifies all non-public events.
/// </summary>
NonPublicEvents = 0x1000,

/// <summary>
/// Specifies all interfaces implemented by the type.
/// </summary>
Interfaces = 0x2000,

/// <summary>
/// Specifies all members.
/// </summary>
All = ~global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// <auto-generated/>
#pragma warning disable
#nullable enable annotations
#if !NET5_0_OR_GREATER

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Diagnostics.CodeAnalysis
{
/// <summary>
/// Indicates that certain members on a specified <see cref="global::System.Type"/> are accessed dynamically,
/// for example through <see cref="global::System.Reflection"/>.
/// </summary>
/// <remarks>
/// This allows tools to understand which members are being accessed during the execution
/// of a program.
///
/// This attribute is valid on members whose type is <see cref="global::System.Type"/> or <see cref="string"/>.
///
/// When this attribute is applied to a location of type <see cref="string"/>, the assumption is
/// that the string represents a fully qualified type name.
///
/// When this attribute is applied to a class, interface, or struct, the members specified
/// can be accessed dynamically on <see cref="global::System.Type"/> instances returned from calling
/// <see cref="object.GetType"/> on instances of that class, interface, or struct.
///
/// If the attribute is applied to a method it's treated as a special case and it implies
/// the attribute should be applied to the "this" parameter of the method. As such the attribute
/// should only be used on instance methods of types assignable to System.Type (or string, but no methods
/// will use it there).
/// </remarks>
[global::System.AttributeUsage(
global::System.AttributeTargets.Field |
global::System.AttributeTargets.ReturnValue |
global::System.AttributeTargets.GenericParameter |
global::System.AttributeTargets.Parameter |
global::System.AttributeTargets.Property |
global::System.AttributeTargets.Method |
global::System.AttributeTargets.Class |
global::System.AttributeTargets.Interface |
global::System.AttributeTargets.Struct,
Inherited = false)]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Diagnostics.Conditional("MULTI_TARGETING_SUPPORT_ATTRIBUTES")]
internal sealed class DynamicallyAccessedMembersAttribute : global::System.Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute"/> class
/// with the specified member types.
/// </summary>
/// <param name="memberTypes">The types of members dynamically accessed.</param>
public DynamicallyAccessedMembersAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes memberTypes)
{
MemberTypes = memberTypes;
}

/// <summary>
/// Gets the <see cref="global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes"/> which specifies the type
/// of members dynamically accessed.
/// </summary>
public global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes MemberTypes { get; }
}
}

#endif
10 changes: 8 additions & 2 deletions src/Transports.AspNetCore/Extensions/GraphQLBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics.CodeAnalysis;

namespace GraphQL;

/// <summary>
Expand All @@ -12,7 +14,9 @@ public static class ServerGraphQLBuilderExtensions
/// Requires <see cref="IHttpContextAccessor"/> to be registered within the dependency injection framework
/// if calling <see cref="DocumentExecuter.ExecuteAsync(ExecutionOptions)"/> directly.
/// </summary>
public static IGraphQLBuilder AddUserContextBuilder<TUserContextBuilder>(this IGraphQLBuilder builder)
public static IGraphQLBuilder AddUserContextBuilder<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TUserContextBuilder>(
this IGraphQLBuilder builder)
where TUserContextBuilder : class, IUserContextBuilder
{
builder.Services.Register<IUserContextBuilder, TUserContextBuilder>(DI.ServiceLifetime.Singleton);
Expand Down Expand Up @@ -107,7 +111,9 @@ private static async Task<ExecutionResult> SetAndExecuteAsync(ExecutionOptions o
/// Registers <typeparamref name="TWebSocketAuthenticationService"/> with the dependency injection framework
/// as a singleton of type <see cref="IWebSocketAuthenticationService"/>.
/// </summary>
public static IGraphQLBuilder AddWebSocketAuthentication<TWebSocketAuthenticationService>(this IGraphQLBuilder builder)
public static IGraphQLBuilder AddWebSocketAuthentication<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TWebSocketAuthenticationService>(
this IGraphQLBuilder builder)
where TWebSocketAuthenticationService : class, IWebSocketAuthenticationService
{
builder.Services.Register<IWebSocketAuthenticationService, TWebSocketAuthenticationService>(DI.ServiceLifetime.Singleton);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.AspNetCore.Builder;

/// <summary>
Expand Down Expand Up @@ -70,7 +72,9 @@ public static IApplicationBuilder UseGraphQL<TSchema>(this IApplicationBuilder b
/// <param name="path">The path to the GraphQL endpoint which defaults to '/graphql'</param>
/// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
/// <returns>The <see cref="IApplicationBuilder"/> received as parameter</returns>
public static IApplicationBuilder UseGraphQL<TMiddleware>(this IApplicationBuilder builder, string path = "/graphql", params object[] args)
public static IApplicationBuilder UseGraphQL<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] TMiddleware>(
this IApplicationBuilder builder, string path = "/graphql", params object[] args)
where TMiddleware : GraphQLHttpMiddleware
=> builder.UseGraphQL<TMiddleware>(new PathString(path), args);

Expand All @@ -82,7 +86,9 @@ public static IApplicationBuilder UseGraphQL<TMiddleware>(this IApplicationBuild
/// <param name="path">The path to the GraphQL endpoint</param>
/// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
/// <returns>The <see cref="IApplicationBuilder"/> received as parameter</returns>
public static IApplicationBuilder UseGraphQL<TMiddleware>(this IApplicationBuilder builder, PathString path, params object[] args)
public static IApplicationBuilder UseGraphQL<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] TMiddleware>(
this IApplicationBuilder builder, PathString path, params object[] args)
where TMiddleware : GraphQLHttpMiddleware
{
return builder.UseWhen(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#if !NETSTANDARD2_0 && !NETCOREAPP2_1

using System.Diagnostics.CodeAnalysis;

namespace Microsoft.AspNetCore.Builder;

/// <summary>
Expand Down Expand Up @@ -47,7 +49,9 @@ public static GraphQLEndpointConventionBuilder MapGraphQL<TSchema>(this IEndpoin
/// <param name="pattern">The route pattern.</param>
/// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
/// <returns>The <see cref="IApplicationBuilder"/> received as parameter</returns>
public static GraphQLEndpointConventionBuilder MapGraphQL<TMiddleware>(this IEndpointRouteBuilder endpoints, string pattern = "graphql", params object[] args)
public static GraphQLEndpointConventionBuilder MapGraphQL<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] TMiddleware>(
this IEndpointRouteBuilder endpoints, string pattern = "graphql", params object[] args)
where TMiddleware : GraphQLHttpMiddleware
{
var requestDelegate = endpoints.CreateApplicationBuilder().UseMiddleware<TMiddleware>(args).Build();
Expand Down
3 changes: 2 additions & 1 deletion src/Transports.AspNetCore/Transports.AspNetCore.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net8.0</TargetFrameworks>
<Description>HTTP middleware for GraphQL</Description>
<PackageTags>GraphQL;middleware</PackageTags>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
</PropertyGroup>

<ItemGroup>
Expand Down
19 changes: 18 additions & 1 deletion src/Ui.Altair/Internal/AltairPageModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Text;
#if NET7_0_OR_GREATER
using System.Text.Json.Serialization;
#endif

namespace GraphQL.Server.Ui.Altair.Internal;

Expand Down Expand Up @@ -58,12 +61,26 @@ private static string StringEncode(string value) => value
.Replace("'", "\\'") // encode ' as \'
.Replace("\"", "\\\""); // encode " as \"

private static string JsonSerialize(object? value)
#nullable disable
private static string JsonSerialize(Dictionary<string, object> value)
#nullable restore
{
#if NETSTANDARD2_0
return Newtonsoft.Json.JsonConvert.SerializeObject(value);
#elif NET7_0_OR_GREATER
return System.Text.Json.JsonSerializer.Serialize(value, SourceGenerationContext.Default.DictionaryStringObject);
#else
return System.Text.Json.JsonSerializer.Serialize(value);
#endif
}
}

#if NET7_0_OR_GREATER
[JsonSerializable(typeof(Dictionary<string, object>))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(string))]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}
#endif
3 changes: 2 additions & 1 deletion src/Ui.Altair/Ui.Altair.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;netstandard2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;netstandard2.0;net8.0</TargetFrameworks>
<Description>GraphQL Altair integration for ASP.NET Core</Description>
<PackageTags>Altair;GraphQL</PackageTags>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
</PropertyGroup>

<ItemGroup>
Expand Down
17 changes: 16 additions & 1 deletion src/Ui.GraphiQL/Internal/GraphiQLPageModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Text;
#if NET7_0_OR_GREATER
using System.Text.Json.Serialization;
#endif

namespace GraphQL.Server.Ui.GraphiQL.Internal;

Expand Down Expand Up @@ -67,12 +70,24 @@ private static string StringEncode(string value) => value
.Replace("'", "\\'") // encode ' as \'
.Replace("\"", "\\\""); // encode " as \"

private static string JsonSerialize(object value)
private static string JsonSerialize(Dictionary<string, object> value)
{
#if NETSTANDARD2_0
return Newtonsoft.Json.JsonConvert.SerializeObject(value);
#elif NET7_0_OR_GREATER
return System.Text.Json.JsonSerializer.Serialize(value, SourceGenerationContext.Default.DictionaryStringObject);
#else
return System.Text.Json.JsonSerializer.Serialize(value);
#endif
}
}

#if NET7_0_OR_GREATER
[JsonSerializable(typeof(Dictionary<string, object>))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(string))]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}
#endif
3 changes: 2 additions & 1 deletion src/Ui.GraphiQL/Ui.GraphiQL.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;netstandard2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;netstandard2.0;net8.0</TargetFrameworks>
<Description>GraphiQL integration for ASP.NET Core</Description>
<PackageTags>GraphiQL;GraphQL</PackageTags>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit 010d522

Please sign in to comment.