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 NativeAOT compatibility for ASP.Net Core in .NET 8 #1124

Merged
merged 5 commits into from
Feb 19, 2024
Merged
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
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
Loading