diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
index 4ff3960d3..80ff26d23 100644
--- a/.github/workflows/ci-build.yml
+++ b/.github/workflows/ci-build.yml
@@ -17,6 +17,7 @@ on:
pull_request:
branches:
- master
+ - 'release/**'
env:
SLN_PATH: ./src/graphql-aspnet.sln
diff --git a/src/ancillary-projects/starwars/starwars-common/GraphControllers/SearchController.cs b/src/ancillary-projects/starwars/starwars-common/GraphControllers/SearchController.cs
index 6d5ad6cbe..149e82b63 100644
--- a/src/ancillary-projects/starwars/starwars-common/GraphControllers/SearchController.cs
+++ b/src/ancillary-projects/starwars/starwars-common/GraphControllers/SearchController.cs
@@ -11,15 +11,12 @@ namespace GraphQL.AspNet.StarwarsAPI.Common.GraphControllers
{
using System.Collections.Generic;
using System.ComponentModel;
- using System.Linq;
using System.Threading.Tasks;
using GraphQL.AspNet.Attributes;
using GraphQL.AspNet.Controllers;
using GraphQL.AspNet.Interfaces.Controllers;
- using GraphQL.AspNet.Schemas.TypeSystem;
using GraphQL.AspNet.StarwarsAPI.Common.Model;
using GraphQL.AspNet.StarwarsAPI.Common.Services;
- using Microsoft.AspNetCore.Authorization;
///
/// A controller to handle search queries across the spectrum of the star wars universe.
@@ -42,7 +39,7 @@ public SearchController(IStarWarsDataService starWarsData)
///
/// The text to search for.
/// Task<IGraphActionResult>.
- [QueryRoot("search","SearchResults", typeof(Droid), typeof(Human), typeof(Starship), TypeExpression = "[Type]")]
+ [QueryRoot("search", "SearchResults", typeof(Droid), typeof(Human), typeof(Starship), TypeExpression = "[Type]")]
[Description("Searches for the specified text as the name of a starship or character (not case sensitive).")]
public async Task GlobalSearch(string searchText = "*")
{
diff --git a/src/ancillary-projects/starwars/starwars-common/GraphControllers/StarshipsController.cs b/src/ancillary-projects/starwars/starwars-common/GraphControllers/StarshipsController.cs
index 18d4b6600..e0a68d05f 100644
--- a/src/ancillary-projects/starwars/starwars-common/GraphControllers/StarshipsController.cs
+++ b/src/ancillary-projects/starwars/starwars-common/GraphControllers/StarshipsController.cs
@@ -21,6 +21,8 @@ namespace GraphQL.AspNet.StarwarsAPI.Common.GraphControllers
using GraphQL.AspNet.Interfaces.Controllers;
using GraphQL.AspNet.StarwarsAPI.Common.Model;
using GraphQL.AspNet.StarwarsAPI.Common.Services;
+ using Microsoft.AspNetCore.Http;
+ using Microsoft.Extensions.DependencyInjection;
///
/// A graph controller responsible for managing starship related data.
@@ -67,7 +69,6 @@ public async Task RetrieveStarship([FromGraphQL("id")] Graph
return this.Ok(starship);
}
-
///
/// Retrieves a droid in the system by their id. Note the use of a different name for the parameter
/// between its exposure in the object graph vs. the formal parameter name used in the C# code.
diff --git a/src/ancillary-projects/starwars/starwars-common/starwars-common.csproj b/src/ancillary-projects/starwars/starwars-common/starwars-common.csproj
index cdf1f0fc5..68d76560f 100644
--- a/src/ancillary-projects/starwars/starwars-common/starwars-common.csproj
+++ b/src/ancillary-projects/starwars/starwars-common/starwars-common.csproj
@@ -1,7 +1,7 @@
-
+
- net8.0;net7.0;netstandard2.0;
+ net8.0;net7.0;latest$(NoWarn);1701;1702;1705;1591;NU1603GraphQL.AspNet.StarwarsAPI.Common
diff --git a/src/graphql-aspnet-subscriptions/Attributes/SubscriptionAttribute.cs b/src/graphql-aspnet-subscriptions/Attributes/SubscriptionAttribute.cs
index 08608a266..0c0c2f2cf 100644
--- a/src/graphql-aspnet-subscriptions/Attributes/SubscriptionAttribute.cs
+++ b/src/graphql-aspnet-subscriptions/Attributes/SubscriptionAttribute.cs
@@ -82,7 +82,7 @@ public SubscriptionAttribute(string template, Type returnType)
/// be sure to supply any additional concrete types so that they may be included in the object graph.
/// Any additional types to include in the object graph on behalf of this method.
public SubscriptionAttribute(string template, Type returnType, params Type[] additionalTypes)
- : base(false, SchemaItemCollections.Subscription, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray())
+ : base(false, SchemaItemCollections.Subscription, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray())
{
this.EventName = null;
}
@@ -93,15 +93,14 @@ public SubscriptionAttribute(string template, Type returnType, params Type[] add
/// The template naming scheme to use to generate a graph field from this method.
/// Name of the union type.
/// The first of two required types to include in the union.
- /// The second of two required types to include in the union.
/// Any additional union types to include.
- public SubscriptionAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes)
+ public SubscriptionAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes)
: base(
false,
SchemaItemCollections.Subscription,
template,
unionTypeName,
- unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray())
+ new Type[] { unionTypeA }.Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray())
{
this.EventName = null;
}
diff --git a/src/graphql-aspnet-subscriptions/Attributes/SubscriptionRootAttribute.cs b/src/graphql-aspnet-subscriptions/Attributes/SubscriptionRootAttribute.cs
index 57833d0b6..6184f7ed2 100644
--- a/src/graphql-aspnet-subscriptions/Attributes/SubscriptionRootAttribute.cs
+++ b/src/graphql-aspnet-subscriptions/Attributes/SubscriptionRootAttribute.cs
@@ -82,7 +82,7 @@ public SubscriptionRootAttribute(string template, Type returnType)
/// be sure to supply any additional concrete types so that they may be included in the object graph.
/// Any additional types to include in the object graph on behalf of this method.
public SubscriptionRootAttribute(string template, Type returnType, params Type[] additionalTypes)
- : base(true, SchemaItemCollections.Subscription, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray())
+ : base(true, SchemaItemCollections.Subscription, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray())
{
this.EventName = null;
}
@@ -93,15 +93,14 @@ public SubscriptionRootAttribute(string template, Type returnType, params Type[]
/// The template naming scheme to use to generate a graph field from this method.
/// Name of the union type.
/// The first of two required types to include in the union.
- /// The second of two required types to include in the union.
/// Any additional union types.
- public SubscriptionRootAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes)
+ public SubscriptionRootAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes)
: base(
true,
SchemaItemCollections.Subscription,
template,
unionTypeName,
- unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray())
+ (new Type[] { unionTypeA }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray())
{
this.EventName = null;
}
diff --git a/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions.cs b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions.cs
new file mode 100644
index 000000000..41ebe64d6
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions.cs
@@ -0,0 +1,185 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using System;
+ using GraphQL.AspNet.Attributes;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Execution;
+ using GraphQL.AspNet.Interfaces.Configuration;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions;
+
+ ///
+ /// Extension methods for defining subscriptions via minimal api.
+ ///
+ public static partial class GraphQLRuntimeSubscriptionDefinitionExtensions
+ {
+ ///
+ /// Creates a new, explicitly resolvable field in the Subscription root object with the given path. This field cannot be
+ /// further extended or nested with other fields via the Mapping API.
+ ///
+ /// The builder representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription(this ISchemaBuilder schemaBuilder, string template)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+
+ return MapSubscription(
+ schemaBuilder.Options,
+ template,
+ null, // unionName
+ null as Delegate);
+ }
+
+ ///
+ /// Creates a new field in the Subscription object with the given path. This field can act as a
+ /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself.
+ ///
+ /// The builder representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// The resolver method to execute when this
+ /// field is requested.
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription(this ISchemaBuilder schemaBuilder, string template, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+ return MapSubscription(
+ schemaBuilder.Options,
+ template,
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Creates a new field in the Subscription object with the given path. This field can act as a
+ /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself.
+ ///
+ /// The builder representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The resolver method to execute when this
+ /// field is requested.
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription(
+ this ISchemaBuilder schemaBuilder,
+ string template,
+ string unionName,
+ Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+
+ return MapSubscription(
+ schemaBuilder.Options,
+ template,
+ unionName,
+ resolverMethod);
+ }
+
+ ///
+ /// Creates a new field in the Subscription root object with the given path. This field can act as a
+ /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself.
+ ///
+ /// The options representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription(
+ this SchemaOptions schemaOptions,
+ string template)
+ {
+ return MapSubscription(
+ schemaOptions,
+ template,
+ null, // unionMethod
+ null as Delegate);
+ }
+
+ ///
+ /// Creates a new, explicitly resolvable field in the Subscription root object with the given path. This field cannot be
+ /// further extended or nested with other fields via the Mapping API.
+ ///
+ /// The options representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// The resolver method to execute when
+ /// this field is requested by a caller.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription(
+ this SchemaOptions schemaOptions,
+ string template,
+ Delegate resolverMethod)
+ {
+ return MapSubscription(
+ schemaOptions,
+ template,
+ null, // unionMethod
+ resolverMethod);
+ }
+
+ ///
+ /// Creates a new, explicitly resolvable field in the Subscription root object with the given path. This field cannot be
+ /// further extended or nested with other fields via the Mapping API.
+ ///
+ /// The options representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The resolver method to execute when
+ /// this field is requested by a caller.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription(
+ this SchemaOptions schemaOptions,
+ string template,
+ string unionName,
+ Delegate resolverMethod)
+ {
+ schemaOptions = Validation.ThrowIfNullOrReturn(schemaOptions, nameof(schemaOptions));
+ template = Validation.ThrowIfNullWhiteSpaceOrReturn(template, nameof(template));
+
+ var fieldTemplate = new RuntimeSubscriptionEnabledFieldGroupTemplate(
+ schemaOptions,
+ template);
+
+ var resolvedField = RuntimeSubscriptionEnabledResolvedFieldDefinition.FromFieldTemplate(fieldTemplate);
+ schemaOptions.AddRuntimeSchemaItem(resolvedField);
+
+ if (!string.IsNullOrWhiteSpace(unionName))
+ resolvedField.AddAttribute(new UnionAttribute(unionName.Trim()));
+
+ resolvedField.AddResolver(unionName, resolverMethod);
+
+ schemaOptions.ServiceCollection?.AddSubscriptionRuntimeFieldExecutionSupport();
+ return resolvedField;
+ }
+
+ ///
+ /// Defines a custom event name for this subscription field. This event name is the key value
+ /// that must be published from a mutation to invoke this subscription for any subscribers.
+ ///
+ ///
+ /// This event name must be unique for this schema.
+ ///
+ /// The field to update.
+ /// Name of the event.
+ /// IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition WithEventName(
+ this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition field,
+ string eventName)
+ {
+ field.EventName = eventName;
+ return field;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_ResolvedFields.cs b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_ResolvedFields.cs
new file mode 100644
index 000000000..207159c67
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_ResolvedFields.cs
@@ -0,0 +1,170 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using System;
+ using GraphQL.AspNet.Attributes;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Interfaces.Controllers;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using Microsoft.AspNetCore.Authorization;
+
+ ///
+ /// Extension methods for defining subscriptions via minimal api.
+ ///
+ public partial class GraphQLRuntimeSubscriptionDefinitionExtensions
+ {
+ ///
+ /// Adds policy-based authorization requirements to the field.
+ ///
+ ///
+ /// This is similar to adding the to a controller method
+ ///
+ /// The field being built.
+ /// The name of the policy to assign via this requirement.
+ /// A comma-seperated list of roles to assign via this requirement.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition RequireAuthorization(
+ this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder,
+ string policyName = null,
+ string roles = null)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ GraphQLRuntimeSchemaItemDefinitionExtensions.RequireAuthorization(fieldBuilder, policyName, roles);
+ return fieldBuilder;
+ }
+
+ ///
+ /// Indicates that the field should allow anonymous access. This will override any potential authorization requirements setup via
+ /// the "MapGroup" methods if this field was created within a group.
+ ///
+ ///
+ /// This is similar to adding the to a controller method
+ ///
+ /// The field being built.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition AllowAnonymous(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ GraphQLRuntimeSchemaItemDefinitionExtensions.AllowAnonymous(fieldBuilder);
+ return fieldBuilder;
+ }
+
+ ///
+ /// Adds a set of possible return types for this field. This is synonymous to using the
+ /// on a controller's action method.
+ ///
+ ///
+ /// This method can be called multiple times. Any new types will be appended to the field. All types added
+ /// must be coercable to the declared return type of the assigned resolver for this field unless this field returns a union; in
+ /// which case the types will be added as union members.
+ ///
+ /// The field being built.
+ /// The first possible type that might be returned by this
+ /// field.
+ /// Any number of additional possible types that
+ /// might be returned by this field.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition AddPossibleTypes(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder, Type firstPossibleType, params Type[] additionalPossibleTypes)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ GraphQLRuntimeSchemaItemDefinitionExtensions.AddPossibleTypes(fieldBuilder, firstPossibleType, additionalPossibleTypes);
+ return fieldBuilder;
+ }
+
+ ///
+ /// Clears all extra defined possible types this field may declare. This will not affect the core type defined by the resolver, if
+ /// a resolver has been defined for this field.
+ ///
+ /// The field builder.
+ /// IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition ClearPossibleTypes(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ GraphQLRuntimeSchemaItemDefinitionExtensions.ClearPossibleTypes(fieldBuilder);
+ return fieldBuilder;
+ }
+
+ ///
+ /// Assigns a custom internal name to this field. This value will be used in error
+ /// messages and log entries instead of an anonymous method name. This can significantly increase readability
+ /// while trying to debug an issue. This value has no bearing on the runtime use of this field. It is cosmetic only.
+ ///
+ ///
+ /// This value does NOT affect the field name as it would appear in a schema. It only effects the internal
+ /// name used in log messages and exception text.
+ ///
+ /// The field being built.
+ /// The value to use as the internal name for this field definition when its
+ /// added to the schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition WithInternalName(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder, string internalName)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ GraphQLRuntimeSchemaItemDefinitionExtensions.WithInternalName(fieldBuilder, internalName);
+ return fieldBuilder;
+ }
+
+ ///
+ /// Indicates this field will return a union and sets the resolver to be used when this field is requested at runtime. The provided
+ /// resolver should return a .
+ ///
+ /// The field being built.
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The delegate to assign as the resolver. This method will be
+ /// parsed to determine input arguments for the field on the target schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition AddResolver(
+ this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder,
+ string unionName,
+ Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ GraphQLRuntimeSchemaItemDefinitionExtensions.AddResolver(fieldBuilder, unionName, resolverMethod);
+ return fieldBuilder;
+ }
+
+ ///
+ /// Sets the resolver to be used when this field is requested at runtime.
+ ///
+ ///
+ /// If this method is called more than once the previously set resolver will be replaced.
+ ///
+ /// The field being built.
+ /// The delegate to assign as the resolver. This method will be
+ /// parsed to determine input arguments for the field on the target schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition AddResolver(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ GraphQLRuntimeSchemaItemDefinitionExtensions.AddResolver(fieldBuilder, resolverMethod);
+ return fieldBuilder;
+ }
+
+ ///
+ /// Sets the resolver to be used when this field is requested at runtime.
+ ///
+ ///
+ /// If this method is called more than once the previously set resolver will be replaced.
+ ///
+ /// The expected, primary return type of the field. Must be provided
+ /// if the supplied delegate returns an .
+ /// The field being built.
+ /// The delegate to assign as the resolver. This method will be
+ /// parsed to determine input arguments for the field on the target schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition AddResolver(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ GraphQLRuntimeSchemaItemDefinitionExtensions.AddResolver(fieldBuilder, resolverMethod);
+ return fieldBuilder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_VirtualFields.cs b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_VirtualFields.cs
new file mode 100644
index 000000000..f864ea604
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_VirtualFields.cs
@@ -0,0 +1,46 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using GraphQL.AspNet.Interfaces.Configuration;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions;
+
+ ///
+ /// Extension methods for defining subscriptions via minimal api.
+ ///
+ public static partial class GraphQLRuntimeSubscriptionDefinitionExtensions
+ {
+ ///
+ /// Begins a new field group for the subscription schema object. All fields created using
+ /// this group will be nested underneath it and inherit any set parameters such as authorization requirements.
+ ///
+ /// The builder to append the Subscription group to.
+ /// The template path for this group.
+ /// IGraphQLRuntimeFieldDefinition.
+ public static IGraphQLRuntimeFieldGroupDefinition MapSubscriptionGroup(this ISchemaBuilder schemaBuilder, string template)
+ {
+ return MapSubscriptionGroup(schemaBuilder?.Options, template);
+ }
+
+ ///
+ /// Begins a new field group for the subscription schema object. All fields created using
+ /// this group will be nested underneath it and inherit any set parameters such as authorization requirements.
+ ///
+ /// The schema options to append the Subscription group to.
+ /// The template path for this group.
+ /// IGraphQLRuntimeFieldDefinition.
+ public static IGraphQLRuntimeFieldGroupDefinition MapSubscriptionGroup(this SchemaOptions schemaOptions, string template)
+ {
+ schemaOptions?.ServiceCollection?.AddSubscriptionRuntimeFieldExecutionSupport();
+ return new RuntimeSubscriptionEnabledFieldGroupTemplate(schemaOptions, template);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Configuration/ServiceCollectionExtensions.cs b/src/graphql-aspnet-subscriptions/Configuration/ServiceCollectionExtensions.cs
new file mode 100644
index 000000000..f9c31dc87
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Configuration/ServiceCollectionExtensions.cs
@@ -0,0 +1,38 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using GraphQL.AspNet.Controllers;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.DependencyInjection.Extensions;
+
+ ///
+ /// Helpful extension methods for the
+ /// when dealing with subscriptions.
+ ///
+ public static class ServiceCollectionExtensions
+ {
+ ///
+ /// Adds support for the execution of runtime subscription field declarations (e.g. minimal api
+ /// defined fields).
+ ///
+ /// The service collection.
+ /// IServiceCollection.
+ public static IServiceCollection AddSubscriptionRuntimeFieldExecutionSupport(this IServiceCollection serviceCollection)
+ {
+ if (serviceCollection != null)
+ {
+ serviceCollection.TryAddTransient();
+ }
+
+ return serviceCollection;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Configuration/SubscriptionBuilderExtensions.cs b/src/graphql-aspnet-subscriptions/Configuration/SubscriptionBuilderExtensions.cs
index 825cd0541..69ba97882 100644
--- a/src/graphql-aspnet-subscriptions/Configuration/SubscriptionBuilderExtensions.cs
+++ b/src/graphql-aspnet-subscriptions/Configuration/SubscriptionBuilderExtensions.cs
@@ -46,7 +46,7 @@ public static ISchemaBuilder AddSubscriptions(
// then publishing adds one additional middleware component
return schemaBuilder
.AddSubscriptionServer(options)
- .AddSubscriptionPublishing(options);
+ .AddSubscriptionPublishing();
}
///
@@ -54,11 +54,9 @@ public static ISchemaBuilder AddSubscriptions(
///
/// The type of schema being configured.
/// The schema builder.
- /// An action function to configure the subscription options.
- /// GraphQL.AspNet.Interfaces.Configuration.ISchemaBuilder<TSchema>.
+ /// ISchemaBuilder<TSchema>.
public static ISchemaBuilder AddSubscriptionPublishing(
- this ISchemaBuilder schemaBuilder,
- Action> options = null)
+ this ISchemaBuilder schemaBuilder)
where TSchema : class, ISchema
{
Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
diff --git a/src/graphql-aspnet-subscriptions/Controllers/ActionResults/CompleteSubscriptionGraphActionResult.cs b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/CompleteSubscriptionGraphActionResult.cs
index 6b5e5b85f..77af70ae7 100644
--- a/src/graphql-aspnet-subscriptions/Controllers/ActionResults/CompleteSubscriptionGraphActionResult.cs
+++ b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/CompleteSubscriptionGraphActionResult.cs
@@ -23,7 +23,7 @@ namespace GraphQL.AspNet.Controllers.ActionResults
/// additional events will be raised.
///
[DebuggerDisplay("Has Object: {_result?.GetType().FriendlyName()}")]
- public class CompleteSubscriptionGraphActionResult : ObjectReturnedGraphActionResult
+ public class CompleteSubscriptionGraphActionResult : OperationCompleteGraphActionResult
{
///
/// Applies the appropriate session information to the field context to instruct
diff --git a/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionEvents.cs b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionEvents.cs
new file mode 100644
index 000000000..b4937248a
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionEvents.cs
@@ -0,0 +1,72 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Controllers.ActionResults
+{
+ using System.Collections.Generic;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Common.Extensions;
+ using GraphQL.AspNet.Execution.Contexts;
+ using GraphQL.AspNet.Execution.Exceptions;
+ using GraphQL.AspNet.Interfaces.Controllers;
+ using GraphQL.AspNet.SubscriptionServer;
+
+ ///
+ /// A helper class to allow the use of common methods
+ /// with non-controller based resolvers for subscription related results.
+ ///
+ public static class SubscriptionEvents
+ {
+ ///
+ /// Publishes an instance of the internal event, informing all graphql-subscriptions that
+ /// are subscribed to the event. If the is
+ /// null the event is automatically canceled.
+ ///
+ /// The resolution context of the field where the event is being published.
+ /// Name of the well-known event to be raised.
+ /// The data object to pass with the event.
+ public static void PublishSubscriptionEvent(this SchemaItemResolutionContext context, string eventName, object dataObject)
+ {
+ Validation.ThrowIfNull(context, nameof(context));
+ Validation.ThrowIfNull(dataObject, nameof(dataObject));
+ eventName = Validation.ThrowIfNullWhiteSpaceOrReturn(eventName, nameof(eventName));
+
+ var contextData = context.Session.Items;
+
+ // add or reference the list of events on the active context
+ var eventsCollectionFound = contextData
+ .TryGetValue(
+ SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION,
+ out var listObject);
+
+ if (!eventsCollectionFound)
+ {
+ listObject = new List(1);
+ contextData.TryAdd(
+ SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION,
+ listObject);
+ }
+
+ var eventList = listObject as IList;
+ if (eventList == null)
+ {
+ throw new GraphExecutionException(
+ $"Unable to cast the context data item '{SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION}' " +
+ $"(type: {listObject?.GetType().FriendlyName() ?? "unknown"}), into " +
+ $"{typeof(IList).FriendlyName()}. Event '{eventName}' could not be published.",
+ context.Request.Origin);
+ }
+
+ lock (eventList)
+ {
+ eventList.Add(new SubscriptionEventProxy(eventName, dataObject));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionGraphActionResult.cs b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionGraphActionResult.cs
new file mode 100644
index 000000000..56866ff92
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionGraphActionResult.cs
@@ -0,0 +1,59 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Controllers.ActionResults
+{
+ using GraphQL.AspNet.Interfaces.Controllers;
+
+ ///
+ /// A helper class to allow the use of common methods
+ /// with non-controller based resolvers for subscription related results.
+ ///
+ public static class SubscriptionGraphActionResult
+ {
+ ///
+ /// When used as an action result from subscription, indicates that the subscription should be skipped
+ /// and the connected client should receive NO data, as if the event never occured.
+ ///
+ /// if set to true, instructs that the
+ /// subscription should also be gracefully end such that no additional events
+ /// are processed after the event is skipped. The client may be informed of this operation if
+ /// supported by its negotiated protocol.
+ ///
+ /// If used as an action result for a non-subscription action (i.e. a query or mutation) a critical
+ /// error will be added to the response and the query will end.
+ ///
+ /// An action result indicating that all field resolution results should be skipped
+ /// and no data should be sent to the connected client.
+ public static IGraphActionResult SkipSubscriptionEvent(bool completeSubscirption = false)
+ {
+ return new SkipSubscriptionEventGraphActionResult(completeSubscirption);
+ }
+
+ ///
+ /// When used as an action result from subscription, resolves the field with the given object
+ /// and indicates that to the client that the subscription should gracefully end when this event completes.
+ /// Once completed, the subscription will be unregsitered and no additional events will
+ /// be raised to this client. The client will be informed of this operation if supported
+ /// by its negotiated protocol.
+ ///
+ /// The object to resolve the field with.
+ ///
+ /// If used as an action result for a non-subscription action (i.e. a query or mutation) a critical
+ /// error will be added to the response and the query will end.
+ ///
+ /// An action result indicating a successful field resolution with the supplied
+ /// and additional information to instruct the subscription server to close the subscription
+ /// once processing is completed.
+ public static IGraphActionResult OkAndComplete(object item = null)
+ {
+ return new CompleteSubscriptionGraphActionResult(item);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Controllers/GraphControllerExtensions.cs b/src/graphql-aspnet-subscriptions/Controllers/GraphControllerExtensions.cs
index 7a4b0d80f..63924231a 100644
--- a/src/graphql-aspnet-subscriptions/Controllers/GraphControllerExtensions.cs
+++ b/src/graphql-aspnet-subscriptions/Controllers/GraphControllerExtensions.cs
@@ -9,14 +9,8 @@
namespace GraphQL.AspNet.Controllers
{
- using System;
- using System.Collections.Generic;
- using GraphQL.AspNet.Common;
- using GraphQL.AspNet.Common.Extensions;
using GraphQL.AspNet.Controllers.ActionResults;
- using GraphQL.AspNet.Execution.Exceptions;
using GraphQL.AspNet.Interfaces.Controllers;
- using GraphQL.AspNet.SubscriptionServer;
///
/// Extension methods to expose subscription to graph controllers.
@@ -33,39 +27,7 @@ public static class GraphControllerExtensions
/// The data object to pass with the event.
public static void PublishSubscriptionEvent(this GraphController controller, string eventName, object dataObject)
{
- Validation.ThrowIfNull(dataObject, nameof(dataObject));
- eventName = Validation.ThrowIfNullWhiteSpaceOrReturn(eventName, nameof(eventName));
-
- var contextData = controller.Context.Session.Items;
-
- // add or reference the list of events on the active context
- var eventsCollectionFound = contextData
- .TryGetValue(
- SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION,
- out var listObject);
-
- if (!eventsCollectionFound)
- {
- listObject = new List(1);
- contextData.TryAdd(
- SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION,
- listObject);
- }
-
- var eventList = listObject as IList;
- if (eventList == null)
- {
- throw new GraphExecutionException(
- $"Unable to cast the context data item '{SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION}' " +
- $"(type: {listObject?.GetType().FriendlyName() ?? "unknown"}), into " +
- $"{typeof(IList).FriendlyName()}. Event '{eventName}' could not be published.",
- controller.Request.Origin);
- }
-
- lock (eventList)
- {
- eventList.Add(new SubscriptionEventProxy(eventName, dataObject));
- }
+ controller?.Context?.PublishSubscriptionEvent(eventName, dataObject);
}
///
@@ -85,7 +47,7 @@ public static void PublishSubscriptionEvent(this GraphController controller, str
/// and no data should be sent to the connected client.
public static IGraphActionResult SkipSubscriptionEvent(this GraphController controller, bool completeSubscirption = false)
{
- return new SkipSubscriptionEventGraphActionResult(completeSubscirption);
+ return SubscriptionGraphActionResult.SkipSubscriptionEvent(completeSubscirption);
}
///
@@ -106,7 +68,7 @@ public static IGraphActionResult SkipSubscriptionEvent(this GraphController cont
/// once processing is completed.
public static IGraphActionResult OkAndComplete(this GraphController controller, object item = null)
{
- return new CompleteSubscriptionGraphActionResult(item);
+ return SubscriptionGraphActionResult.OkAndComplete(item);
}
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Controllers/SubscriptionEnabledRuntimeFieldExecutionController.cs b/src/graphql-aspnet-subscriptions/Controllers/SubscriptionEnabledRuntimeFieldExecutionController.cs
new file mode 100644
index 000000000..42ca9f07a
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Controllers/SubscriptionEnabledRuntimeFieldExecutionController.cs
@@ -0,0 +1,22 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Controllers
+{
+ using GraphQL.AspNet.Attributes;
+
+ ///
+ /// A special controller instance for executing subscription runtime configured controller
+ /// actions (e.g. minimal api defined fields).
+ ///
+ [GraphRoot]
+ internal class SubscriptionEnabledRuntimeFieldExecutionController : GraphController
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphQLSchemaFactory.cs b/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphQLSchemaFactory.cs
new file mode 100644
index 000000000..184172912
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphQLSchemaFactory.cs
@@ -0,0 +1,50 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Engine
+{
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Interfaces.Schema;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using GraphQL.AspNet.Schemas.Generation.TypeMakers;
+ using GraphQL.AspNet.Schemas.Generation.TypeTemplates;
+ using GraphQL.AspNet.Schemas.TypeMakers;
+
+ ///
+ /// A schema creation factory that understands and can generate subscription types.
+ ///
+ /// The type of the schema being generated.
+ public class SubscriptionEnabledGraphQLSchemaFactory : DefaultGraphQLSchemaFactory
+ where TSchema : class, ISchema
+ {
+ ///
+ protected override GraphTypeMakerFactory CreateMakerFactory()
+ {
+ return new SubscriptionEnabledGraphTypeMakerFactory(this.Schema);
+ }
+
+ ///
+ protected override void AddRuntimeFieldDefinition(IGraphQLRuntimeResolvedFieldDefinition fieldDefinition)
+ {
+ Validation.ThrowIfNull(fieldDefinition, nameof(fieldDefinition));
+
+ if (fieldDefinition is IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition subField)
+ {
+ var template = new RuntimeSubscriptionGraphControllerTemplate(subField);
+ template.Parse();
+ template.ValidateOrThrow();
+
+ this.AddController(template);
+ return;
+ }
+
+ base.AddRuntimeFieldDefinition(fieldDefinition);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphTypeMakerProvider.cs b/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphTypeMakerProvider.cs
deleted file mode 100644
index 5dfb03d58..000000000
--- a/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphTypeMakerProvider.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-// *************************************************************
-// project: graphql-aspnet
-// --
-// repo: https://github.com/graphql-aspnet
-// docs: https://graphql-aspnet.github.io
-// --
-// License: MIT
-// *************************************************************
-
-namespace GraphQL.AspNet.Engine
-{
- using GraphQL.AspNet.Engine.TypeMakers;
- using GraphQL.AspNet.Interfaces.Engine;
- using GraphQL.AspNet.Interfaces.Schema;
-
- ///
- /// An upgraded "type maker" factory that adds low level subscription field support
- /// to the type system.
- ///
- public class SubscriptionEnabledGraphTypeMakerProvider : DefaultGraphTypeMakerProvider
- {
- ///
- public override IGraphFieldMaker CreateFieldMaker(ISchema schema)
- {
- return new SubscriptionEnabledGraphFieldMaker(schema);
- }
- }
-}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledTypeTemplateProvider.cs b/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledTypeTemplateProvider.cs
deleted file mode 100644
index 9e9214e43..000000000
--- a/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledTypeTemplateProvider.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// *************************************************************
-// project: graphql-aspnet
-// --
-// repo: https://github.com/graphql-aspnet
-// docs: https://graphql-aspnet.github.io
-// --
-// License: MIT
-// *************************************************************
-
-namespace GraphQL.AspNet.Engine
-{
- using System;
- using GraphQL.AspNet.Common;
- using GraphQL.AspNet.Controllers;
- using GraphQL.AspNet.Interfaces.Internal;
- using GraphQL.AspNet.Internal.TypeTemplates;
- using GraphQL.AspNet.Schemas.TypeSystem;
-
- ///
- /// A template provider that adds the ability to parse subscription marked fields on graph controllers
- /// in order to add them to the schema.
- ///
- public class SubscriptionEnabledTypeTemplateProvider : DefaultTypeTemplateProvider
- {
- ///
- /// Makes a graph template from the given type.
- ///
- /// Type of the object.
- /// The kind of graph type to parse for.
- /// IGraphItemTemplate.
- protected override IGraphTypeTemplate MakeTemplate(Type objectType, TypeKind kind)
- {
- if (Validation.IsCastable(objectType))
- return new SubscriptionGraphControllerTemplate(objectType);
-
- return base.MakeTemplate(objectType, kind);
- }
- }
-}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Interfaces/Schema/RuntimeDefinitions/IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition.cs b/src/graphql-aspnet-subscriptions/Interfaces/Schema/RuntimeDefinitions/IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition.cs
new file mode 100644
index 000000000..e0c24ca39
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Interfaces/Schema/RuntimeDefinitions/IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition.cs
@@ -0,0 +1,24 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions
+{
+ ///
+ /// A special marker interface to indicate a runtime resolved field is subscription enabled.
+ ///
+ public interface IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition : IGraphQLRuntimeResolvedFieldDefinition
+ {
+ ///
+ /// Gets or sets the name of the event that must be published from a mutation to invoke this
+ /// subscription.
+ ///
+ /// The name of the published event that identifies this subscription.
+ string EventName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledFieldGroupTemplate.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledFieldGroupTemplate.cs
new file mode 100644
index 000000000..496e584a2
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledFieldGroupTemplate.cs
@@ -0,0 +1,63 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions
+{
+ using GraphQL.AspNet.Configuration;
+ using GraphQL.AspNet.Execution;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+
+ ///
+ /// A with the ability to create subscription
+ /// enabled minimal api fields.
+ ///
+ public sealed class RuntimeSubscriptionEnabledFieldGroupTemplate : RuntimeFieldGroupTemplateBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The primary schema options where this field (and its children) will be
+ /// instantiated.
+ /// The path template describing this field.
+ public RuntimeSubscriptionEnabledFieldGroupTemplate(
+ SchemaOptions options, string pathTemplate)
+ : base(
+ options,
+ SchemaItemCollections.Subscription,
+ pathTemplate)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The field from which this entity is being added.
+ /// The partial path template to be appended to
+ /// the parent's already defined template.
+ public RuntimeSubscriptionEnabledFieldGroupTemplate(
+ IGraphQLRuntimeFieldGroupDefinition parentField, string fieldSubTemplate)
+ : base(parentField, fieldSubTemplate)
+ {
+ }
+
+ ///
+ public override IGraphQLRuntimeFieldGroupDefinition MapChildGroup(string pathTemplate)
+ {
+ return new RuntimeSubscriptionEnabledFieldGroupTemplate(this, pathTemplate);
+ }
+
+ ///
+ public override IGraphQLRuntimeResolvedFieldDefinition MapField(string pathTemplate)
+ {
+ var field = new RuntimeSubscriptionEnabledResolvedFieldDefinition(this, pathTemplate, null);
+ this.Options.AddRuntimeSchemaItem(field);
+ return field;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledResolvedFieldDefinition.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledResolvedFieldDefinition.cs
new file mode 100644
index 000000000..8f8648a47
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledResolvedFieldDefinition.cs
@@ -0,0 +1,122 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions
+{
+ using System;
+ using GraphQL.AspNet.Attributes;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Configuration;
+ using GraphQL.AspNet.Execution;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using GraphQL.AspNet.Schemas.Structural;
+
+ ///
+ /// A runtime field definition that supports additional required data items to properly setup a subscription via
+ /// a "minimal api" call.
+ ///
+ public class RuntimeSubscriptionEnabledResolvedFieldDefinition : RuntimeResolvedFieldDefinition, IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition
+ {
+ private string _customEventName;
+
+ ///
+ /// Converts the unresolved field into a resolved field. The newly generated field
+ /// will NOT be attached to any schema and will not have an assigned resolver.
+ ///
+ /// The field template.
+ /// IGraphQLResolvedFieldTemplate.
+ internal static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition FromFieldTemplate(IGraphQLRuntimeFieldGroupDefinition fieldTemplate)
+ {
+ Validation.ThrowIfNull(fieldTemplate, nameof(fieldTemplate));
+ var field = new RuntimeSubscriptionEnabledResolvedFieldDefinition(
+ fieldTemplate.Options,
+ fieldTemplate.Route,
+ null);
+
+ foreach (var attrib in fieldTemplate.Attributes)
+ field.AddAttribute(attrib);
+
+ return field;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The parent field builder to which this new, resolved field
+ /// will be appended.
+ /// The template part to append to the parent field's template.
+ /// Name of the event that must be published to invoke this subscription
+ /// field.
+ public RuntimeSubscriptionEnabledResolvedFieldDefinition(
+ IGraphQLRuntimeFieldGroupDefinition parentFieldBuilder,
+ string fieldSubTemplate,
+ string eventName)
+ : base(parentFieldBuilder, fieldSubTemplate)
+ {
+ this.EventName = eventName?.Trim();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The schema options to which this field is being added.
+ /// The full route to use for this item.
+ /// Name of the event that must be published to invoke this subscription
+ /// field.
+ private RuntimeSubscriptionEnabledResolvedFieldDefinition(
+ SchemaOptions schemaOptions,
+ SchemaItemPath route,
+ string eventName)
+ : base(schemaOptions, route)
+ {
+ this.EventName = eventName?.Trim();
+ }
+
+ ///
+ protected override Attribute CreatePrimaryAttribute()
+ {
+ var (collection, path) = this.Route;
+ if (collection == SchemaItemCollections.Subscription)
+ {
+ return new SubscriptionRootAttribute(path, this.ReturnType)
+ {
+ InternalName = this.InternalName,
+ EventName = this.EventName,
+ };
+ }
+
+ return base.CreatePrimaryAttribute();
+ }
+
+ ///
+ public string EventName
+ {
+ get
+ {
+ return _customEventName;
+ }
+
+ set
+ {
+ _customEventName = value?.Trim();
+ if (!string.IsNullOrWhiteSpace(_customEventName))
+ return;
+
+ var (_, path) = this.Route;
+
+ // turns path1/path2/path3 => path1_path2_path3
+ _customEventName = string.Join(
+ "_",
+ path.Split(
+ Constants.Routing.PATH_SEPERATOR,
+ StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/RuntimeSubscriptionGraphControllerTemplate.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/RuntimeSubscriptionGraphControllerTemplate.cs
new file mode 100644
index 000000000..f2d2b8eed
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/RuntimeSubscriptionGraphControllerTemplate.cs
@@ -0,0 +1,72 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates
+{
+ using System.Collections.Generic;
+ using GraphQL.AspNet.Controllers;
+ using GraphQL.AspNet.Execution.Exceptions;
+ using GraphQL.AspNet.Interfaces.Internal;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+
+ ///
+ /// A "controller template" representing a single runtime configured subscription field (e.g. minimal api).
+ /// This template is never cached.
+ ///
+ internal class RuntimeSubscriptionGraphControllerTemplate : SubscriptionGraphControllerTemplate
+ {
+ private readonly IMemberInfoProvider _fieldProvider;
+ private readonly IGraphQLRuntimeResolvedFieldDefinition _fieldDefinition;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A single, runtime configured, field definition
+ /// to templatize for a specfic schema.
+ public RuntimeSubscriptionGraphControllerTemplate(IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldDefinition)
+ : base(typeof(SubscriptionEnabledRuntimeFieldExecutionController))
+ {
+ _fieldDefinition = fieldDefinition;
+ if (fieldDefinition.Resolver?.Method != null)
+ {
+ _fieldProvider = new MemberInfoProvider(
+ fieldDefinition.Resolver.Method,
+ new RuntimeSchemaItemAttributeProvider(fieldDefinition));
+ }
+ }
+
+ ///
+ protected override IEnumerable GatherPossibleFieldTemplates()
+ {
+ yield return _fieldProvider;
+ }
+
+ ///
+ protected override bool CouldBeGraphField(IMemberInfoProvider fieldProvider)
+ {
+ return fieldProvider != null && fieldProvider == _fieldProvider;
+ }
+
+ ///
+ public override void ValidateOrThrow(bool validateChildren = true)
+ {
+ if (_fieldDefinition?.Resolver?.Method == null)
+ {
+ throw new GraphTypeDeclarationException(
+ $"Unable to templatize the runtime field definition of '{_fieldDefinition?.Route.Path ?? "~null~"}' the resolver " +
+ $"is not properly configured.");
+ }
+
+ base.ValidateOrThrow(validateChildren);
+ }
+
+ ///
+ public override ItemSource TemplateSource => ItemSource.Runtime;
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs
similarity index 80%
rename from src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs
rename to src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs
index 10d854b22..5df0cff9a 100644
--- a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs
+++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs
@@ -7,12 +7,13 @@
// License: MIT
// *************************************************************
-namespace GraphQL.AspNet.Internal.TypeTemplates
+namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates
{
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
+ using GraphQL.AspNet;
using GraphQL.AspNet.Attributes;
using GraphQL.AspNet.Common.Extensions;
using GraphQL.AspNet.Controllers;
@@ -32,8 +33,8 @@ public class SubscriptionControllerActionGraphFieldTemplate : ControllerActionGr
///
/// Initializes a new instance of the class.
///
- /// The parent.
- /// The method information.
+ /// The controller that owns this action.
+ /// The method information to be templatized.
public SubscriptionControllerActionGraphFieldTemplate(
IGraphControllerTemplate parent,
MethodInfo methodInfo)
@@ -42,6 +43,22 @@ public SubscriptionControllerActionGraphFieldTemplate(
this.EventName = null;
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The controller that owns this action.
+ /// The method information to be templatized.
+ /// A custom, external attribute provider to use instead for extracting
+ /// configuration attributes instead of the provider on .
+ public SubscriptionControllerActionGraphFieldTemplate(
+ IGraphControllerTemplate parent,
+ MethodInfo methodInfo,
+ ICustomAttributeProvider attributeProvider)
+ : base(parent, methodInfo, attributeProvider)
+ {
+ this.EventName = null;
+ }
+
///
protected override void ParseTemplateDefinition()
{
@@ -82,14 +99,14 @@ public override void ValidateOrThrow(bool validateChildren = true)
if (string.IsNullOrWhiteSpace(this.EventName))
{
throw new GraphTypeDeclarationException(
- $"Invalid subscription action declaration. The method '{this.InternalFullName}' does not " +
+ $"Invalid subscription action declaration. The method '{this.InternalName}' does not " +
$"have an event name.");
}
if (!Constants.RegExPatterns.NameRegex.IsMatch(this.EventName))
{
throw new GraphTypeDeclarationException(
- $"Invalid subscription action declaration. The method '{this.InternalFullName}' declares " +
+ $"Invalid subscription action declaration. The method '{this.InternalName}' declares " +
$"a custom event name of '{this.EventName}'. However, the event name must conform to " +
$"standard graphql naming rules. (Regex: {Constants.RegExPatterns.NameRegex} )");
}
@@ -98,17 +115,17 @@ public override void ValidateOrThrow(bool validateChildren = true)
if (this.Method.GetParameters().Where(x => x.HasAttribute()).Count() > 1)
{
throw new GraphTypeDeclarationException(
- $"Invalid subscription action declaration. The method '{this.InternalFullName}' decorates more" +
+ $"Invalid subscription action declaration. The method '{this.InternalName}' decorates more" +
$"than one parameter with {typeof(SubscriptionSourceAttribute).FriendlyName()}. At most one parameter " +
$"can be attributed with {typeof(SubscriptionSourceAttribute).FriendlyName()}");
}
// ensure there is only one param marked as the source object
- var sourceArgument = this.Arguments.SingleOrDefault(x => x.ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult));
+ var sourceArgument = this.Arguments.SingleOrDefault(x => x.ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult));
if (sourceArgument == null)
{
throw new GraphTypeDeclarationException(
- $"Invalid subscription action declaration. The method '{this.InternalFullName}' must " +
+ $"Invalid subscription action declaration. The method '{this.InternalName}' must " +
$"declare 1 (and only 1) parameter of type '{this.SourceObjectType.FriendlyName()}' which will be populated" +
"with the source data raised by a subscription event at runtime. Alternately use " +
$"{typeof(SubscriptionSourceAttribute).FriendlyName()} to explicitly assign a source data parameter.");
@@ -118,7 +135,7 @@ public override void ValidateOrThrow(bool validateChildren = true)
}
///
- protected override GraphArgumentTemplate CreateInputArgument(ParameterInfo paramInfo)
+ protected override GraphArgumentTemplate CreateArgument(ParameterInfo paramInfo)
{
if (this.Route.RootCollection == Execution.SchemaItemCollections.Subscription)
{
@@ -128,7 +145,7 @@ protected override GraphArgumentTemplate CreateInputArgument(ParameterInfo param
_explicitlyDeclaredAsSubscriptionSourceType != null);
}
- return base.CreateInputArgument(paramInfo);
+ return base.CreateArgument(paramInfo);
}
///
diff --git a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphArgumentTemplate.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphArgumentTemplate.cs
similarity index 96%
rename from src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphArgumentTemplate.cs
rename to src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphArgumentTemplate.cs
index 05483c3bd..3f37ff266 100644
--- a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphArgumentTemplate.cs
+++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphArgumentTemplate.cs
@@ -7,7 +7,7 @@
// License: MIT
// *************************************************************
-namespace GraphQL.AspNet.Internal.TypeTemplates
+namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates
{
using System.Reflection;
using GraphQL.AspNet.Attributes;
diff --git a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphControllerTemplate.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphControllerTemplate.cs
similarity index 62%
rename from src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphControllerTemplate.cs
rename to src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphControllerTemplate.cs
index 624df3f99..2fd4bc100 100644
--- a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphControllerTemplate.cs
+++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphControllerTemplate.cs
@@ -7,7 +7,7 @@
// License: MIT
// *************************************************************
-namespace GraphQL.AspNet.Internal.TypeTemplates
+namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates
{
using System;
using System.Diagnostics;
@@ -33,21 +33,20 @@ public SubscriptionGraphControllerTemplate(Type controllerType)
{
}
- ///
- /// When overridden in a child, allows the class to create custom template that inherit from
- /// to provide additional functionality or garuntee a certian type structure for all methods on this object template.
- ///
- /// The method information.
- /// IGraphFieldTemplate.
- protected override IGraphFieldTemplate CreateMethodFieldTemplate(MethodInfo methodInfo)
+ ///
+ protected override IGraphFieldTemplate CreateFieldTemplate(IMemberInfoProvider member)
{
- if (methodInfo == null)
+ if (member?.MemberInfo == null || !(member.MemberInfo is MethodInfo))
return null;
- if (methodInfo.HasAttribute() || methodInfo.HasAttribute())
- return new SubscriptionControllerActionGraphFieldTemplate(this, methodInfo);
+ if (member?.MemberInfo != null &&
+ member.MemberInfo is MethodInfo methodInfo &&
+ (member.AttributeProvider.HasAttribute() || member.AttributeProvider.HasAttribute()))
+ {
+ return new SubscriptionControllerActionGraphFieldTemplate(this, methodInfo, member.AttributeProvider);
+ }
- return base.CreateMethodFieldTemplate(methodInfo);
+ return base.CreateFieldTemplate(member);
}
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Engine/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs b/src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs
similarity index 70%
rename from src/graphql-aspnet-subscriptions/Engine/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs
rename to src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs
index 2456c393f..e6a019715 100644
--- a/src/graphql-aspnet-subscriptions/Engine/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs
+++ b/src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs
@@ -6,15 +6,17 @@
// --
// License: MIT
// *************************************************************
-namespace GraphQL.AspNet.Engine.TypeMakers
+
+namespace GraphQL.AspNet.Schemas.TypeMakers
{
using System.Collections.Generic;
using GraphQL.AspNet.Configuration.Formatting;
using GraphQL.AspNet.Execution;
+ using GraphQL.AspNet.Interfaces.Engine;
using GraphQL.AspNet.Interfaces.Internal;
using GraphQL.AspNet.Interfaces.Schema;
- using GraphQL.AspNet.Internal.TypeTemplates;
- using GraphQL.AspNet.Schemas;
+ using GraphQL.AspNet.Schemas.Generation.TypeMakers;
+ using GraphQL.AspNet.Schemas.Generation.TypeTemplates;
using GraphQL.AspNet.Schemas.TypeSystem;
using GraphQL.AspNet.Security;
@@ -25,16 +27,17 @@ namespace GraphQL.AspNet.Engine.TypeMakers
public class SubscriptionEnabledGraphFieldMaker : GraphFieldMaker
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// The schema.
- public SubscriptionEnabledGraphFieldMaker(ISchema schema)
- : base(schema)
+ /// The schema instance to reference when creating fields.
+ /// A maker that can make arguments declared on this field.
+ public SubscriptionEnabledGraphFieldMaker(ISchema schema, IGraphArgumentMaker argMaker)
+ : base(schema, argMaker)
{
}
///
- protected override MethodGraphField InstantiateField(
+ protected override MethodGraphField CreateFieldInstance(
GraphNameFormatter formatter,
IGraphFieldTemplate template,
List securityGroups)
@@ -46,10 +49,14 @@ protected override MethodGraphField InstantiateField(
{
var directives = template.CreateAppliedDirectives();
+ var schemaTypeName = this.PrepareTypeName(template);
+ var typeExpression = template.TypeExpression.CloneTo(schemaTypeName);
+
return new SubscriptionMethodGraphField(
formatter.FormatFieldName(template.Name),
- template.TypeExpression.CloneTo(formatter.FormatGraphTypeName(template.TypeExpression.TypeName)),
+ typeExpression,
template.Route,
+ template.InternalName,
template.ObjectType,
template.DeclaredReturnType,
template.Mode,
@@ -59,7 +66,7 @@ protected override MethodGraphField InstantiateField(
directives);
}
- return base.InstantiateField(formatter, template, securityGroups);
+ return base.CreateFieldInstance(formatter, template, securityGroups);
}
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphTypeMakerFactory.cs b/src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphTypeMakerFactory.cs
new file mode 100644
index 000000000..61df8b054
--- /dev/null
+++ b/src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphTypeMakerFactory.cs
@@ -0,0 +1,61 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Schemas.TypeMakers
+{
+ using System;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Controllers;
+ using GraphQL.AspNet.Interfaces.Engine;
+ using GraphQL.AspNet.Interfaces.Internal;
+ using GraphQL.AspNet.Interfaces.Schema;
+ using GraphQL.AspNet.Schemas.Generation.TypeMakers;
+ using GraphQL.AspNet.Schemas.Generation.TypeTemplates;
+ using GraphQL.AspNet.Schemas.TypeSystem;
+
+ ///
+ /// An upgraded "type maker" factory that adds low level subscription field support
+ /// to the type system.
+ ///
+ public class SubscriptionEnabledGraphTypeMakerFactory : GraphTypeMakerFactory
+ {
+ private readonly ISchema _schemaInstance;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The schema instance to reference when making
+ /// types.
+ public SubscriptionEnabledGraphTypeMakerFactory(ISchema schemaInstance)
+ : base(schemaInstance)
+ {
+ _schemaInstance = Validation.ThrowIfNullOrReturn(schemaInstance, nameof(schemaInstance));
+ }
+
+ ///
+ public override IGraphTypeTemplate MakeTemplate(Type objectType, TypeKind? kind = null)
+ {
+ if (Validation.IsCastable(objectType))
+ {
+ var template = new SubscriptionGraphControllerTemplate(objectType);
+ template.Parse();
+ template.ValidateOrThrow();
+ return template;
+ }
+
+ return base.MakeTemplate(objectType, kind);
+ }
+
+ ///
+ public override IGraphFieldMaker CreateFieldMaker()
+ {
+ return new SubscriptionEnabledGraphFieldMaker(_schemaInstance, this.CreateArgumentMaker());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/Schemas/TypeSystem/SubscriptionMethodGraphField.cs b/src/graphql-aspnet-subscriptions/Schemas/TypeSystem/SubscriptionMethodGraphField.cs
index 1d9a156d9..1f9434078 100644
--- a/src/graphql-aspnet-subscriptions/Schemas/TypeSystem/SubscriptionMethodGraphField.cs
+++ b/src/graphql-aspnet-subscriptions/Schemas/TypeSystem/SubscriptionMethodGraphField.cs
@@ -27,17 +27,20 @@ public class SubscriptionMethodGraphField : MethodGraphField, ISubscriptionGraph
/// Name of the field in the type declaration..
/// The meta data about how this type field is implemented.
/// The formal route to this field in the object graph.
+ /// The fully qualified name of the method this field respresents, as it was declared
+ /// in C# code.
/// The .NET type of the item or items that represent the graph type returned by this field.
/// The .NET type as it was declared on the property which generated this field..
/// The execution mode of this field.
/// The resolver.
- /// The security policies.
+ /// The security policies applied to this field.
/// Alterante name of the event that has been assigned to this field.
/// The directives to be applied to this field when its added to a schema.
public SubscriptionMethodGraphField(
string fieldName,
GraphTypeExpression typeExpression,
SchemaItemPath route,
+ string internalFullName,
Type objectType = null,
Type declaredReturnType = null,
Execution.FieldResolutionMode mode = Execution.FieldResolutionMode.PerSourceItem,
@@ -45,7 +48,7 @@ public SubscriptionMethodGraphField(
IEnumerable securityPolicies = null,
string eventName = null,
IAppliedDirectiveCollection directives = null)
- : base(fieldName, typeExpression, route, objectType, declaredReturnType, mode, resolver, securityPolicies, directives)
+ : base(fieldName, internalFullName, typeExpression, route, declaredReturnType, objectType, mode, resolver, securityPolicies, directives)
{
this.EventName = eventName;
}
@@ -57,6 +60,7 @@ protected override MethodGraphField CreateNewInstance(IGraphType parent)
this.Name,
this.TypeExpression.Clone(),
parent.Route.CreateChild(this.Name),
+ this.InternalName,
this.ObjectType,
this.DeclaredReturnType,
this.Mode,
diff --git a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionPublisherSchemaExtension.cs b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionPublisherSchemaExtension.cs
index 8133410cf..2b37eb246 100644
--- a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionPublisherSchemaExtension.cs
+++ b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionPublisherSchemaExtension.cs
@@ -10,12 +10,16 @@
namespace GraphQL.AspNet.SubscriptionServer
{
using System;
+ using System.Linq;
using GraphQL.AspNet.Configuration;
using GraphQL.AspNet.Engine;
using GraphQL.AspNet.Interfaces.Configuration;
+ using GraphQL.AspNet.Interfaces.Engine;
using GraphQL.AspNet.Interfaces.Schema;
using GraphQL.AspNet.Schemas.TypeSystem;
using Microsoft.AspNetCore.Builder;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.DependencyInjection.Extensions;
///
/// A schema extension encapsulating the ability for a given schema to publish subscription events from
@@ -45,24 +49,6 @@ public void Configure(SchemaOptions options)
{
_primaryOptions = options;
_primaryOptions.DeclarationOptions.AllowedOperations.Add(GraphOperationType.Subscription);
-
- // swap out the master providers for the ones that includes
- // support for the subscription action type
- if (!(GraphQLProviders.TemplateProvider is SubscriptionEnabledTypeTemplateProvider))
- GraphQLProviders.TemplateProvider = new SubscriptionEnabledTypeTemplateProvider();
-
- if (!(GraphQLProviders.GraphTypeMakerProvider is SubscriptionEnabledGraphTypeMakerProvider))
- GraphQLProviders.GraphTypeMakerProvider = new SubscriptionEnabledGraphTypeMakerProvider();
- }
-
- ///
- /// Invokes this instance to perform any final setup requirements as part of
- /// its configuration during startup.
- ///
- /// The application builder, no middleware will be registered if not supplied.
- /// The service provider to use.
- public void UseExtension(IApplicationBuilder app = null, IServiceProvider serviceProvider = null)
- {
- }
+ }
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionReceiverSchemaExtension.cs b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionReceiverSchemaExtension.cs
index facb10443..bdefe3bee 100644
--- a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionReceiverSchemaExtension.cs
+++ b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionReceiverSchemaExtension.cs
@@ -10,10 +10,13 @@
namespace GraphQL.AspNet.SubscriptionServer
{
using System;
+ using System.Linq;
using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Common.Extensions;
using GraphQL.AspNet.Configuration;
using GraphQL.AspNet.Engine;
using GraphQL.AspNet.Interfaces.Configuration;
+ using GraphQL.AspNet.Interfaces.Engine;
using GraphQL.AspNet.Interfaces.Internal;
using GraphQL.AspNet.Interfaces.Logging;
using GraphQL.AspNet.Interfaces.Schema;
@@ -89,16 +92,27 @@ public void Configure(SchemaOptions options)
$"authorization method. (Current authorization method is \"{_schemaBuilder.Options.AuthorizationOptions.Method}\")");
}
- // swap out the master templating provider for the one that includes
- // support for the subscription action type if and only if the developer has not
- // already registered their own custom one
- if (GraphQLProviders.TemplateProvider == null || GraphQLProviders.TemplateProvider.GetType() == typeof(DefaultTypeTemplateProvider))
- GraphQLProviders.TemplateProvider = new SubscriptionEnabledTypeTemplateProvider();
+ // swap out the master schema factory to one that includes
+ // support for the subscription action type
+ var existingFactories = _schemaBuilder
+ .Options
+ .ServiceCollection
+ .FirstOrDefault(x => x.ServiceType == typeof(IGraphQLSchemaFactory));
- // swap out the master graph type maker to its "subscription enabled" version
- // if and only if the developer has not already registered their own custom instance
- if (GraphQLProviders.GraphTypeMakerProvider == null || GraphQLProviders.GraphTypeMakerProvider.GetType() == typeof(DefaultGraphTypeMakerProvider))
- GraphQLProviders.GraphTypeMakerProvider = new SubscriptionEnabledGraphTypeMakerProvider();
+ if (existingFactories != null)
+ {
+ _schemaBuilder.Options
+ .ServiceCollection
+ .RemoveAll(typeof(IGraphQLSchemaFactory));
+ }
+
+ _schemaBuilder.Options
+ .ServiceCollection
+ .TryAdd(
+ new ServiceDescriptor(
+ typeof(IGraphQLSchemaFactory),
+ typeof(SubscriptionEnabledGraphQLSchemaFactory),
+ ServiceLifetime.Transient));
// Update the query execution pipeline
// ------------------------------------------
diff --git a/src/graphql-aspnet-subscriptions/graphql-aspnet-subscriptions.csproj b/src/graphql-aspnet-subscriptions/graphql-aspnet-subscriptions.csproj
index 9e4407520..ab7590b5f 100644
--- a/src/graphql-aspnet-subscriptions/graphql-aspnet-subscriptions.csproj
+++ b/src/graphql-aspnet-subscriptions/graphql-aspnet-subscriptions.csproj
@@ -1,4 +1,4 @@
-
+
@@ -8,6 +8,8 @@
A package to support subscriptions for GraphQL ASP.NET. Provides the required functionality to setup a websocket connection and perform graphql subscriptions over web sockets.
+
+
@@ -19,12 +21,4 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/graphql-aspnet/Attributes/FromGraphQLAttribute.cs b/src/graphql-aspnet/Attributes/FromGraphQLAttribute.cs
index 6299ce590..f2333608a 100644
--- a/src/graphql-aspnet/Attributes/FromGraphQLAttribute.cs
+++ b/src/graphql-aspnet/Attributes/FromGraphQLAttribute.cs
@@ -59,5 +59,15 @@ public FromGraphQLAttribute(string argumentName)
///
/// The type expression to assign to this argument.
public string TypeExpression { get; set; }
+
+ ///
+ /// Gets or sets a customized name to refer to this parameter in all log entries and error messages.
+ ///
+ ///
+ /// When not supplied the name defaults to a combination of the class + method + parameter naem. (e.g. 'MyObject.Method1.Param1'). This
+ /// can be especially helpful when working with runtime defined fields (e.g. minimal api).
+ ///
+ /// The name to refer to this parameter on internal messaging.
+ public string InternalName { get; set; }
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Attributes/GraphEnumValueAttribute.cs b/src/graphql-aspnet/Attributes/GraphEnumValueAttribute.cs
index 15aa30d22..47dd61edd 100644
--- a/src/graphql-aspnet/Attributes/GraphEnumValueAttribute.cs
+++ b/src/graphql-aspnet/Attributes/GraphEnumValueAttribute.cs
@@ -40,5 +40,14 @@ public GraphEnumValueAttribute(string name)
///
/// The name given to this enum value.
public string Name { get; }
+
+ ///
+ /// Gets or sets a customized name to refer to this .NET enum value in all log entries and error messages.
+ ///
+ ///
+ /// When not supplied the name defaults to the qualified name of the enum value. (e.g. 'MyEnum.Value1').
+ ///
+ /// The name to refer to this enum value on internal messaging.
+ public string InternalName { get; set; }
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Attributes/GraphFieldAttribute.cs b/src/graphql-aspnet/Attributes/GraphFieldAttribute.cs
index 8cbdf7224..44490e736 100644
--- a/src/graphql-aspnet/Attributes/GraphFieldAttribute.cs
+++ b/src/graphql-aspnet/Attributes/GraphFieldAttribute.cs
@@ -33,9 +33,10 @@ public GraphFieldAttribute()
///
/// Initializes a new instance of the class.
///
- /// Name of the field.
- public GraphFieldAttribute(string name)
- : this(false, SchemaItemCollections.Types, name)
+ /// The template naming scheme to use to generate a graph field from this method or property. The exact name may be altered on a per schema
+ /// basis depending on field name formatting rules etc.
+ public GraphFieldAttribute(string template)
+ : this(false, SchemaItemCollections.Types, template)
{
}
@@ -97,14 +98,14 @@ protected GraphFieldAttribute(
///
/// Gets the name of the union this field defines, if any.
///
- /// The name of the union.
+ /// The name of the union type to create when multiple return types are possible.
public string UnionTypeName { get; }
///
/// Gets a value indicating whether this instance represents a template
/// pathed from its root operation or if it is intended to be nested with another fragment.
///
- /// true if this instance is root fragment; otherwise, false.
+ /// true if this instance is rooted to a top level operation; otherwise, false.
public bool IsRootFragment { get; }
///
@@ -145,7 +146,17 @@ protected GraphFieldAttribute(
/// Gets the mode indicating how the runtime should process
/// the objects resolving this field.
///
- /// The mode.
+ /// The execution mode of this field when it is resolved by the runtime.
public virtual FieldResolutionMode ExecutionMode => FieldResolutionMode.PerSourceItem;
+
+ ///
+ /// Gets or sets a customized name to refer to this field in all log entries and error messages.
+ ///
+ ///
+ /// When not supplied the name defaults to the fully qualified method or property name. (e.g. 'MyObject.MyMethod'). This
+ /// can be especially helpful when working with runtime defined fields (e.g. minimal api).
+ ///
+ /// The name to refer to this field on internal messaging.
+ public string InternalName { get; set; }
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Attributes/GraphSkipAttribute.cs b/src/graphql-aspnet/Attributes/GraphSkipAttribute.cs
index e7101fc70..17c3bdd98 100644
--- a/src/graphql-aspnet/Attributes/GraphSkipAttribute.cs
+++ b/src/graphql-aspnet/Attributes/GraphSkipAttribute.cs
@@ -21,7 +21,8 @@ namespace GraphQL.AspNet.Attributes
| AttributeTargets.Field
| AttributeTargets.Enum
| AttributeTargets.Class
- | AttributeTargets.Struct)]
+ | AttributeTargets.Struct
+ | AttributeTargets.Parameter)]
public class GraphSkipAttribute : Attribute
{
// Implementation note: This attribute purposefully does
diff --git a/src/graphql-aspnet/Attributes/GraphTypeAttribute.cs b/src/graphql-aspnet/Attributes/GraphTypeAttribute.cs
index 05b4dab6f..814d801e9 100644
--- a/src/graphql-aspnet/Attributes/GraphTypeAttribute.cs
+++ b/src/graphql-aspnet/Attributes/GraphTypeAttribute.cs
@@ -111,5 +111,15 @@ public TemplateDeclarationRequirements FieldDeclarationRequirements
///
/// true if publish; otherwise, false.
public bool Publish { get; set; }
+
+ ///
+ /// Gets or sets a customized name to refer to this .NET type in all log entries and error messages.
+ ///
+ ///
+ /// When not supplied the name defaults to the fully qualified class, struct or primative name. (e.g. 'MyObject', 'Person'). This
+ /// can be especially helpful when working with runtime defined fields (e.g. minimal api).
+ ///
+ /// The name to refer to this field on internal messaging.
+ public string InternalName { get; set; }
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Attributes/MutationAttribute.cs b/src/graphql-aspnet/Attributes/MutationAttribute.cs
index 3e199d712..a36183d9c 100644
--- a/src/graphql-aspnet/Attributes/MutationAttribute.cs
+++ b/src/graphql-aspnet/Attributes/MutationAttribute.cs
@@ -36,7 +36,17 @@ public MutationAttribute()
///
/// The template naming scheme to use to generate a graph field from this method.
public MutationAttribute(string template)
- : this(template, null)
+ : this(template, null as Type)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The template naming scheme to use to generate a graph field from this method.
+ /// Name of the union type.
+ public MutationAttribute(string template, string unionTypeName)
+ : this(template, unionTypeName, null)
{
}
@@ -82,7 +92,7 @@ public MutationAttribute(string template, Type returnType)
/// be sure to supply any additional concrete types so that they may be included in the object graph.
/// Any additional types to include in the object graph on behalf of this method.
public MutationAttribute(string template, Type returnType, params Type[] additionalTypes)
- : base(false, SchemaItemCollections.Mutation, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray())
+ : base(false, SchemaItemCollections.Mutation, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray())
{
}
@@ -92,15 +102,14 @@ public MutationAttribute(string template, Type returnType, params Type[] additio
/// The template naming scheme to use to generate a graph field from this method.
/// Name of the union type.
/// The first of two required types to include in the union.
- /// The second of two required types to include in the union.
/// Any additional union types.
- public MutationAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes)
+ public MutationAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes)
: base(
false,
SchemaItemCollections.Mutation,
template,
unionTypeName,
- unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray())
+ (new Type[] { unionTypeA }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray())
{
}
}
diff --git a/src/graphql-aspnet/Attributes/MutationRootAttribute.cs b/src/graphql-aspnet/Attributes/MutationRootAttribute.cs
index 9cebba929..a1b8bb873 100644
--- a/src/graphql-aspnet/Attributes/MutationRootAttribute.cs
+++ b/src/graphql-aspnet/Attributes/MutationRootAttribute.cs
@@ -35,7 +35,17 @@ public MutationRootAttribute()
///
/// The template naming scheme to use to generate a graph field from this method.
public MutationRootAttribute(string template)
- : this(template, null)
+ : this(template, null as Type)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The template naming scheme to use to generate a graph field from this method.
+ /// Name of the union type.
+ public MutationRootAttribute(string template, string unionTypeName)
+ : this(template, unionTypeName, null, null)
{
}
@@ -81,7 +91,7 @@ public MutationRootAttribute(string template, Type returnType)
/// be sure to supply any additional concrete types so that they may be included in the object graph.
/// Any additional types to include in the object graph on behalf of this method.
public MutationRootAttribute(string template, Type returnType, params Type[] additionalTypes)
- : base(true, SchemaItemCollections.Mutation, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray())
+ : base(true, SchemaItemCollections.Mutation, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray())
{
}
@@ -91,10 +101,14 @@ public MutationRootAttribute(string template, Type returnType, params Type[] add
/// The template naming scheme to use to generate a graph field from this method.
/// Name of the union type.
/// The first of two required types to include in the union.
- /// The second of two required types to include in the union.
/// Any additional union types.
- public MutationRootAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes)
- : base(true, SchemaItemCollections.Mutation, template, unionTypeName, unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray())
+ public MutationRootAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes)
+ : base(
+ true,
+ SchemaItemCollections.Mutation,
+ template,
+ unionTypeName,
+ (new Type[] { unionTypeA }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray())
{
}
}
diff --git a/src/graphql-aspnet/Attributes/PossibleTypesAttribute.cs b/src/graphql-aspnet/Attributes/PossibleTypesAttribute.cs
index edce03ab0..4543c7c09 100644
--- a/src/graphql-aspnet/Attributes/PossibleTypesAttribute.cs
+++ b/src/graphql-aspnet/Attributes/PossibleTypesAttribute.cs
@@ -12,7 +12,6 @@ namespace GraphQL.AspNet.Attributes
using System;
using System.Collections.Generic;
using System.Linq;
- using GraphQL.AspNet.Common.Extensions;
///
/// When an graph field returns an interface, use this attribute
@@ -32,13 +31,16 @@ public class PossibleTypesAttribute : Attribute
/// Any additional possible types.
public PossibleTypesAttribute(Type firstPossibleType, params Type[] additionalPossibleTypes)
{
- this.PossibleTypes = firstPossibleType.AsEnumerable().Concat(additionalPossibleTypes).Where(x => x != null).ToArray();
+ this.PossibleTypes = new Type[] { firstPossibleType }
+ .Concat(additionalPossibleTypes ?? Enumerable.Empty())
+ .Where(x => x != null)
+ .ToList();
}
///
/// Gets the possible types this field may return under its declared interface.
///
/// The possible types.
- public IReadOnlyList PossibleTypes { get; }
+ public IList PossibleTypes { get; }
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Attributes/QueryAttribute.cs b/src/graphql-aspnet/Attributes/QueryAttribute.cs
index 0e2ff7aa8..fe9083886 100644
--- a/src/graphql-aspnet/Attributes/QueryAttribute.cs
+++ b/src/graphql-aspnet/Attributes/QueryAttribute.cs
@@ -36,7 +36,17 @@ public QueryAttribute()
///
/// The template naming scheme to use to generate a graph field from this method.
public QueryAttribute(string template)
- : this(template, null)
+ : this(template, null as Type)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The template naming scheme to use to generate a graph field from this method.
+ /// Name of the union type.
+ public QueryAttribute(string template, string unionTypeName)
+ : this(template, unionTypeName, null)
{
}
@@ -82,7 +92,7 @@ public QueryAttribute(string template, Type returnType)
/// be sure to supply any additional concrete types so that they may be included in the object graph.
/// Any additional types to include in the object graph on behalf of this method.
public QueryAttribute(string template, Type returnType, params Type[] additionalTypes)
- : base(false, SchemaItemCollections.Query, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray())
+ : base(false, SchemaItemCollections.Query, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray())
{
}
@@ -91,16 +101,15 @@ public QueryAttribute(string template, Type returnType, params Type[] additional
///
/// The template naming scheme to use to generate a graph field from this method.
/// Name of the union type.
- /// The first of two required types to include in the union.
- /// The second of two required types to include in the union.
+ /// The first type to include in the union.
/// Any additional union types to include.
- public QueryAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes)
+ public QueryAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes)
: base(
false,
SchemaItemCollections.Query,
template,
unionTypeName,
- unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray())
+ (new Type[] { unionTypeA }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray())
{
}
}
diff --git a/src/graphql-aspnet/Attributes/QueryRootAttribute.cs b/src/graphql-aspnet/Attributes/QueryRootAttribute.cs
index fe6d73102..1614ef2cb 100644
--- a/src/graphql-aspnet/Attributes/QueryRootAttribute.cs
+++ b/src/graphql-aspnet/Attributes/QueryRootAttribute.cs
@@ -35,7 +35,17 @@ public QueryRootAttribute()
///
/// The template naming scheme to use to generate a graph field from this method.
public QueryRootAttribute(string template)
- : this(template, null)
+ : this(template, null as Type)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The template naming scheme to use to generate a graph field from this method.
+ /// Name of the union type.
+ public QueryRootAttribute(string template, string unionTypeName)
+ : this(template, unionTypeName, null, null)
{
}
@@ -81,7 +91,11 @@ public QueryRootAttribute(string template, Type returnType)
/// be sure to supply any additional concrete types so that they may be included in the object graph.
/// Any additional types to include in the object graph on behalf of this method.
public QueryRootAttribute(string template, Type returnType, params Type[] additionalTypes)
- : base(true, SchemaItemCollections.Query, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray())
+ : base(
+ true,
+ SchemaItemCollections.Query,
+ template,
+ (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray())
{
}
@@ -91,15 +105,14 @@ public QueryRootAttribute(string template, Type returnType, params Type[] additi
/// The template naming scheme to use to generate a graph field from this method.
/// Name of the union type.
/// The first of two required types to include in the union.
- /// The second of two required types to include in the union.
/// Any additional union types.
- public QueryRootAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes)
+ public QueryRootAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes)
: base(
true,
SchemaItemCollections.Query,
template,
unionTypeName,
- unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray())
+ (new Type[] { unionTypeA }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray())
{
}
}
diff --git a/src/graphql-aspnet/Attributes/UnionAttribute.cs b/src/graphql-aspnet/Attributes/UnionAttribute.cs
new file mode 100644
index 000000000..61a4ec3b3
--- /dev/null
+++ b/src/graphql-aspnet/Attributes/UnionAttribute.cs
@@ -0,0 +1,90 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Attributes
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using GraphQL.AspNet.Interfaces.Controllers;
+ using GraphQL.AspNet.Interfaces.Schema;
+ using GraphQL.AspNet.Schemas.TypeSystem;
+
+ ///
+ /// An attribute that, when applied to an action method, field or property
+ /// declares that the field is to return one of multiple possible types.
+ ///
+ ///
+ /// Fields applying this attribute should return a or
+ /// for maximum compatiability.
+ ///
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
+ public class UnionAttribute : GraphAttributeBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the union as it should appear in the schema.
+ /// The first member type to include in the union.
+ /// Additional member types to include in the union.
+ /// All Unions must declare at least two member types that will be included in the union.
+ public UnionAttribute(string unionName, Type firstUnionMemberType, params Type[] otherUnionMembers)
+ {
+ this.UnionName = unionName?.Trim();
+
+ var list = new List(2 + otherUnionMembers.Length);
+ list.Add(firstUnionMemberType);
+ list.AddRange(otherUnionMembers);
+ this.UnionMemberTypes = list;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A type that inherits from or implements which
+ /// declares all required information about the referenced union.
+ public UnionAttribute(Type unionProxyType)
+ : this()
+ {
+ this.UnionName = null;
+ if (unionProxyType != null)
+ this.UnionMemberTypes.Add(unionProxyType);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name to assign to this union.
+ public UnionAttribute(string unionName)
+ : this()
+ {
+ this.UnionName = unionName;
+ }
+
+ ///
+ /// Prevents a default instance of the class from being created.
+ ///
+ private UnionAttribute()
+ {
+ this.UnionMemberTypes = new List();
+ }
+
+ ///
+ /// Gets or sets the name of the new union to create within the schema. This union name must be a valid graph name.
+ ///
+ /// The name of the union as it will appear in the schema.
+ public string UnionName { get; set; }
+
+ ///
+ /// Gets or sets the concrete types of the objects that may be returned by this field.
+ ///
+ /// All union member types to be included in this union.
+ public IList UnionMemberTypes { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Common/Extensions/AttributeExtensions.cs b/src/graphql-aspnet/Common/Extensions/AttributeExtensions.cs
new file mode 100644
index 000000000..89dd03e14
--- /dev/null
+++ b/src/graphql-aspnet/Common/Extensions/AttributeExtensions.cs
@@ -0,0 +1,44 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Common.Extensions
+{
+ using System;
+ using System.Linq;
+ using Microsoft.AspNetCore.Authorization;
+
+ ///
+ /// Extension methods for working with the attributes supplied by the library.
+ ///
+ public static class AttributeExtensions
+ {
+ ///
+ /// Inspects the given attribute's attributes to determine if its allowed to be applied more than once
+ /// to a given entity.
+ ///
+ ///
+ /// Used primarily for runtime field configuration to ensure correct usage within the templating system.
+ ///
+ /// The attribute to check.
+ /// true if this instance can be applied to an entity multiple times the specified attribute; otherwise, false.
+ public static bool CanBeAppliedMultipleTimes(this Attribute attribute)
+ {
+ var usage = attribute.GetType().GetCustomAttributes(typeof(AttributeUsageAttribute), true)
+ .Cast()
+ .ToList();
+
+ // the default is always false
+ if (usage.Count == 0)
+ return false;
+
+ // take the first found instance in the inheritance stack
+ return usage[0].AllowMultiple;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Common/Extensions/TypeExtensions.cs b/src/graphql-aspnet/Common/Extensions/TypeExtensions.cs
index 165d0be32..9c3b4e426 100644
--- a/src/graphql-aspnet/Common/Extensions/TypeExtensions.cs
+++ b/src/graphql-aspnet/Common/Extensions/TypeExtensions.cs
@@ -106,16 +106,16 @@ public static TAttribute SingleAttributeOrDefault(this Enum enumValu
/// is also returned.
///
/// The type of the attribute to check for.
- /// The type to inspect.
+ /// The type to inspect.
/// When true, look up the hierarchy chain for the inherited custom attribute..
/// TAttribute.
- public static TAttribute SingleAttributeOrDefault(this ICustomAttributeProvider type, bool inherit = false)
+ public static TAttribute SingleAttributeOrDefault(this ICustomAttributeProvider attribProvider, bool inherit = false)
where TAttribute : Attribute
{
- if (type == null)
+ if (attribProvider == null)
return null;
- var attribs = type.GetCustomAttributes(typeof(TAttribute), inherit)
+ var attribs = attribProvider.GetCustomAttributes(typeof(TAttribute), inherit)
.Where(x => x.GetType() == typeof(TAttribute))
.Take(2);
@@ -130,16 +130,16 @@ public static TAttribute SingleAttributeOrDefault(this ICustomAttrib
/// that is castable to the given type, the first instance encountered is returned.
///
/// The type of the attribute to check for.
- /// The type to inspect.
+ /// The type to inspect.
/// When true, look up the hierarchy chain for the inherited custom attribute..
/// TAttribute.
- public static TAttribute FirstAttributeOfTypeOrDefault(this ICustomAttributeProvider type, bool inherit = false)
+ public static TAttribute FirstAttributeOfTypeOrDefault(this ICustomAttributeProvider attribProvider, bool inherit = false)
where TAttribute : Attribute
{
- if (type == null)
+ if (attribProvider == null)
return null;
- var attribs = type.GetCustomAttributes(typeof(TAttribute), inherit).Where(x => x.GetType() == typeof(TAttribute)).Take(2);
+ var attribs = attribProvider.GetCustomAttributes(typeof(TAttribute), inherit).Where(x => x.GetType() == typeof(TAttribute)).Take(2);
return attribs.FirstOrDefault() as TAttribute;
}
@@ -148,16 +148,16 @@ public static TAttribute FirstAttributeOfTypeOrDefault(this ICustomA
/// that matches the type condition, null is returned.
///
/// The type of the attribute to check for.
- /// The type to inspect.
+ /// The type to inspect.
/// When true, look up the hierarchy chain for the inherited custom attribute..
/// TAttribute.
- public static TAttribute SingleAttributeOfTypeOrDefault(this ICustomAttributeProvider type, bool inherit = false)
+ public static TAttribute SingleAttributeOfTypeOrDefault(this ICustomAttributeProvider attribProvider, bool inherit = false)
where TAttribute : Attribute
{
- if (type == null)
+ if (attribProvider == null)
return null;
- var attribs = type.GetCustomAttributes(typeof(TAttribute), inherit).Cast().Take(2);
+ var attribs = attribProvider.GetCustomAttributes(typeof(TAttribute), inherit).Cast().Take(2);
if (attribs.Count() == 1)
return attribs.Single();
@@ -169,16 +169,16 @@ public static TAttribute SingleAttributeOfTypeOrDefault(this ICustom
/// the given an empty set is returned.
///
/// The type of the attribute to check for.
- /// The type from which to extract attributes.
+ /// The type from which to extract attributes.
/// When true, look up the hierarchy chain for the inherited custom attribute..
/// TAttribute.
- public static IEnumerable AttributesOfType(this ICustomAttributeProvider type, bool inherit = false)
+ public static IEnumerable AttributesOfType(this ICustomAttributeProvider attribProvider, bool inherit = false)
where TAttribute : Attribute
{
- if (type == null)
+ if (attribProvider == null)
return null;
- var attribs = type.GetCustomAttributes(typeof(TAttribute), inherit)
+ var attribs = attribProvider.GetCustomAttributes(typeof(TAttribute), inherit)
.Where(x => Validation.IsCastable(x.GetType(), typeof(TAttribute)))
.Cast();
@@ -189,28 +189,28 @@ public static IEnumerable AttributesOfType(this ICustomA
/// Determines if the given type had the attribute defined at least once.
///
/// The type of the attribute.
- /// The type from which to check.
+ /// The type from which to check.
/// When true, look up the hierarchy chain for the inherited custom attribute..
/// TAttribute.
- public static bool HasAttribute(this ICustomAttributeProvider type, bool inherit = false)
+ public static bool HasAttribute(this ICustomAttributeProvider attribProvider, bool inherit = false)
where TAttribute : Attribute
{
- return type.HasAttribute(typeof(TAttribute), inherit);
+ return attribProvider.HasAttribute(typeof(TAttribute), inherit);
}
///
/// Determines if the given type had the attribute defined at least once.
///
- /// The type to check.
+ /// The type to check.
/// Type of the attribute.
/// When true, look up the hierarchy chain for the inherited custom attribute..
/// TAttribute.
- public static bool HasAttribute(this ICustomAttributeProvider type, Type attributeType, bool inherit = false)
+ public static bool HasAttribute(this ICustomAttributeProvider attribProvider, Type attributeType, bool inherit = false)
{
- if (type == null || attributeType == null || !Validation.IsCastable(attributeType))
+ if (attribProvider == null || attributeType == null || !Validation.IsCastable(attributeType))
return false;
- return type.IsDefined(attributeType, inherit);
+ return attribProvider.IsDefined(attributeType, inherit);
}
///
diff --git a/src/graphql-aspnet/Common/Generics/InstanceFactory.cs b/src/graphql-aspnet/Common/Generics/InstanceFactory.cs
index b6891c270..a4ca2d443 100644
--- a/src/graphql-aspnet/Common/Generics/InstanceFactory.cs
+++ b/src/graphql-aspnet/Common/Generics/InstanceFactory.cs
@@ -24,7 +24,8 @@ namespace GraphQL.AspNet.Common.Generics
internal static class InstanceFactory
{
private static readonly ConcurrentDictionary, ObjectActivator> CACHED_OBJECT_CREATORS;
- private static readonly ConcurrentDictionary CACHED_METHOD_INVOKERS;
+ private static readonly ConcurrentDictionary CACHED_INSTANCE_METHOD_INVOKERS;
+ private static readonly ConcurrentDictionary CACHED_STATIC_METHOD_INVOKERS;
private static readonly ConcurrentDictionary CACHED_PROPERTY_SETTER_INVOKERS;
private static readonly ConcurrentDictionary CACHED_PROPERTY_GETTER_INVOKERS;
@@ -34,7 +35,8 @@ internal static class InstanceFactory
static InstanceFactory()
{
CACHED_OBJECT_CREATORS = new ConcurrentDictionary, ObjectActivator>();
- CACHED_METHOD_INVOKERS = new ConcurrentDictionary();
+ CACHED_INSTANCE_METHOD_INVOKERS = new ConcurrentDictionary();
+ CACHED_STATIC_METHOD_INVOKERS = new ConcurrentDictionary();
CACHED_PROPERTY_SETTER_INVOKERS = new ConcurrentDictionary();
CACHED_PROPERTY_GETTER_INVOKERS = new ConcurrentDictionary();
}
@@ -45,7 +47,8 @@ static InstanceFactory()
public static void Clear()
{
CACHED_OBJECT_CREATORS.Clear();
- CACHED_METHOD_INVOKERS.Clear();
+ CACHED_INSTANCE_METHOD_INVOKERS.Clear();
+ CACHED_STATIC_METHOD_INVOKERS.Clear();
CACHED_PROPERTY_SETTER_INVOKERS.Clear();
CACHED_PROPERTY_GETTER_INVOKERS.Clear();
}
@@ -169,17 +172,17 @@ public static PropertyGetterCollection CreatePropertyGetterInvokerCollection(Typ
}
///
- /// Creates a compiled lamda expression to invoke an arbitrary method via a common set of parameters. This lamda is cached for any future use greatly speeding up
- /// the invocation.
+ /// Creates a compiled lamda expression to invoke an arbitrary method via a common set of parameters on an object instance.
+ /// This lamda is cached for any future use greatly speeding up the invocation.
///
/// The method information.
/// Func<System.Object, System.Object[], System.Object>.
- public static MethodInvoker CreateInstanceMethodInvoker(MethodInfo methodInfo)
+ public static InstanceMethodInvoker CreateInstanceMethodInvoker(MethodInfo methodInfo)
{
if (methodInfo == null)
return null;
- if (CACHED_METHOD_INVOKERS.TryGetValue(methodInfo, out var invoker))
+ if (CACHED_INSTANCE_METHOD_INVOKERS.TryGetValue(methodInfo, out var invoker))
return invoker;
if (methodInfo.IsStatic)
@@ -194,17 +197,114 @@ public static MethodInvoker CreateInstanceMethodInvoker(MethodInfo methodInfo)
"This instance creator only supports methods with a return value.");
}
- invoker = CreateMethodInvoker(methodInfo);
- CACHED_METHOD_INVOKERS.TryAdd(methodInfo, invoker);
+ invoker = CompileInstanceMethodInvoker(methodInfo);
+ CACHED_INSTANCE_METHOD_INVOKERS.TryAdd(methodInfo, invoker);
return invoker;
}
///
- /// Creates the method invoker expression and compiles the resultant lambda.
+ /// Creates a compiled lamda expression to invoke an arbitrary static method via a common set of parameters.
+ /// This lamda is cached for any future use greatly speeding up the invocation.
+ ///
+ /// The method to create an expression for.
+ /// StaticMethodInvoker.
+ public static StaticMethodInvoker CreateStaticMethodInvoker(MethodInfo methodInfo)
+ {
+ if (methodInfo == null)
+ return null;
+
+ if (CACHED_STATIC_METHOD_INVOKERS.TryGetValue(methodInfo, out var invoker))
+ return invoker;
+
+ if (!methodInfo.IsStatic)
+ {
+ throw new ArgumentException($"The method '{methodInfo.Name}' on type '{methodInfo.DeclaringType.FriendlyName()}' is NOT static " +
+ "and cannot be used to create a static method reference.");
+ }
+
+ if (methodInfo.ReturnType == typeof(void))
+ {
+ throw new ArgumentException($"The method '{methodInfo.Name}' on type '{methodInfo.DeclaringType.FriendlyName()}' does not return a value. " +
+ "This instance creator only supports methods with a return value.");
+ }
+
+ invoker = CompileStaticMethodInvoker(methodInfo);
+ CACHED_STATIC_METHOD_INVOKERS.TryAdd(methodInfo, invoker);
+ return invoker;
+ }
+
+ ///
+ /// Compiles the method invoker expression for a static method and compiles the resultant lambda.
///
/// The method to create an expression for.
/// MethodInvoker.
- private static MethodInvoker CreateMethodInvoker(MethodInfo methodInfo)
+ private static StaticMethodInvoker CompileStaticMethodInvoker(MethodInfo methodInfo)
+ {
+ Validation.ThrowIfNull(methodInfo, nameof(methodInfo));
+
+ // -------------------------------------------
+ // Function call parameters
+ // -------------------------------------------
+ // e.g.
+ // object[] paramSet = new object[];
+ // "var returnValue = invoke(paramSet);
+ //
+ // create a single param of type object[] tha will hold the variable length of method arguments being passed in
+ ParameterExpression inputArguments = Expression.Parameter(typeof(object[]), "args");
+
+ // -------------------------------------------
+ // method body
+ // -------------------------------------------
+
+ // invocation parameters to pass to the method
+ ParameterInfo[] paramsInfo = methodInfo.GetParameters();
+
+ // a set of expressions that represents
+ // a casting of each supplied object to its specific type for invocation
+ Expression[] argsAssignments = new Expression[paramsInfo.Length];
+
+ // pick each arg from the supplied method parameters and create a expression
+ // that casts them into the required type for the parameter position on the method
+ for (var i = 0; i < paramsInfo.Length; i++)
+ {
+ Expression index = Expression.Constant(i);
+ Type paramType = paramsInfo[i].ParameterType;
+ Expression paramAccessorExp = Expression.ArrayIndex(inputArguments, index);
+ if (paramType.IsValueType)
+ argsAssignments[i] = Expression.Unbox(paramAccessorExp, paramType);
+ else
+ argsAssignments[i] = Expression.Convert(paramAccessorExp, paramType);
+ }
+
+ // a direct call to the method on the invokable object with the supplied parameters
+ var methodCall = Expression.Call(methodInfo, argsAssignments);
+
+ // Execute the method call and assign its output to returnedVariable
+ var returnedVariable = Expression.Variable(methodInfo.ReturnType);
+ var methodCallResultAssigned = Expression.Assign(returnedVariable, methodCall);
+
+ // box the method result into an object
+ var boxedResult = Expression.Variable(typeof(object));
+ var boxedResultAssignment = Expression.Assign(boxedResult, Expression.Convert(returnedVariable, typeof(object)));
+
+ // assembly the method body
+ var methodBody = Expression.Block(
+ new ParameterExpression[] { returnedVariable, boxedResult },
+ methodCallResultAssigned,
+ boxedResultAssignment,
+ boxedResult);
+
+ // Create lambda expression that accepts the "this" parameter and the args from the user.
+ var lambda = Expression.Lambda(methodBody, new ParameterExpression[] { inputArguments });
+ return lambda.Compile();
+ }
+
+ ///
+ /// Compiles the method invoker expression for an instance based method and compiles the resultant lambda.
+ ///
+ /// The method to create an expression for.
+ /// InstanceMethodInvoker.
+ private static InstanceMethodInvoker CompileInstanceMethodInvoker(MethodInfo methodInfo)
{
Validation.ThrowIfNull(methodInfo, nameof(methodInfo));
@@ -224,7 +324,7 @@ private static MethodInvoker CreateMethodInvoker(MethodInfo methodInfo)
// -------------------------------------------
// cast the input object to its required Type
var castedObjectToInvokeOn = Expression.Variable(declaredType);
- var castOperation = Expression.Assign(castedObjectToInvokeOn, Expression.Convert(objectToInvokeOn, declaredType));
+ var castingOperation = Expression.Assign(castedObjectToInvokeOn, Expression.Convert(objectToInvokeOn, declaredType));
// invocation parameters to pass to the method
ParameterInfo[] paramsInfo = methodInfo.GetParameters();
@@ -265,14 +365,14 @@ private static MethodInvoker CreateMethodInvoker(MethodInfo methodInfo)
var methodBody = Expression.Block(
new ParameterExpression[] { castedObjectToInvokeOn, returnedVariable, boxedResult },
- castOperation,
+ castingOperation,
methodCallResultAssigned,
boxedResultAssignment,
reassignReffedValue,
boxedResult);
// Create lambda expression that accepts the "this" parameter and the args from the user.
- var lambda = Expression.Lambda(methodBody, new ParameterExpression[] { objectToInvokeOn, inputArguments });
+ var lambda = Expression.Lambda(methodBody, new ParameterExpression[] { objectToInvokeOn, inputArguments });
return lambda.Compile();
}
@@ -304,7 +404,7 @@ public static object CreateInstance(Type type, params object[] args)
// in combination with Roger Johanson: https://rogerjohansson.blog/2008/02/28/linq-expressions-creating-objects/ .
// -----------------------------
if (args == null)
- return CreateInstance(type);
+ return CreateInstance(type, new object[0]);
if ((args.Length > 3)
|| (args.Length > 0 && args[0] == null)
@@ -458,19 +558,26 @@ private static IReadOnlyList CreateConstructorTypeList(params Type[] types
/// Gets the creators cached to this application instance.
///
/// The cached creators.
- public static IReadOnlyDictionary, ObjectActivator> ObjectCreators => CACHED_OBJECT_CREATORS;
+ public static IReadOnlyDictionary, ObjectActivator> ObjectCreators => CACHED_OBJECT_CREATORS;
+
+ ///
+ /// Gets the collection of cached method invokers for fast invocation of methods on target
+ /// objects in this app instance.
+ ///
+ /// The method invokers.
+ public static IReadOnlyDictionary InstanceMethodInvokers => CACHED_INSTANCE_METHOD_INVOKERS;
///
- /// Gets the collection of cached method invokers for fast invocation in this app instance.
+ /// Gets the collection of cached static method invokers for fast invocation in this app instance.
///
/// The method invokers.
- public static IReadOnlyDictionary MethodInvokers => CACHED_METHOD_INVOKERS;
+ public static IReadOnlyDictionary StaticMethodInvokers => CACHED_STATIC_METHOD_INVOKERS;
///
/// Gets the collection of cached property setter invokers for fast invocation in this app instance.
///
/// The cached collection of property "setter" invokers.
- public static IReadOnlyDictionary PropertySetterInvokers => CACHED_PROPERTY_SETTER_INVOKERS;
+ public static IReadOnlyDictionary PropertySetterInvokers => CACHED_PROPERTY_SETTER_INVOKERS;
///
/// Gets the collection of cached property getter invokers for fast invocation in this app instance.
diff --git a/src/graphql-aspnet/Common/Generics/MethodInvoker.cs b/src/graphql-aspnet/Common/Generics/MethodInvoker.cs
index 6a2ae172c..61a0c1cdc 100644
--- a/src/graphql-aspnet/Common/Generics/MethodInvoker.cs
+++ b/src/graphql-aspnet/Common/Generics/MethodInvoker.cs
@@ -6,13 +6,22 @@
// --
// License: MIT
// *************************************************************
+
namespace GraphQL.AspNet.Common.Generics
{
///
/// A representation of a compiled lamda to invoke a method on an instance of an object.
///
- /// The object instance to invoke on.
+ /// The object instance to invoke the delegated method on.
/// The parameters to pass the method call.
/// The result of the call.
- internal delegate object MethodInvoker(ref object instanceToInvokeOn, params object[] methodParameters);
+ internal delegate object InstanceMethodInvoker(ref object instanceToInvokeOn, params object[] methodParameters);
+
+ ///
+ /// A representation of a compiled lamda to invoke a static method not attached to a specific
+ /// object instance.
+ ///
+ /// The parameters to pass the method call.
+ /// The result of the call.
+ internal delegate object StaticMethodInvoker(params object[] methodParameters);
}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Common/JsonNodes/JsonNodeException.cs b/src/graphql-aspnet/Common/JsonNodes/JsonNodeException.cs
index 94edc1fe3..25046d727 100644
--- a/src/graphql-aspnet/Common/JsonNodes/JsonNodeException.cs
+++ b/src/graphql-aspnet/Common/JsonNodes/JsonNodeException.cs
@@ -7,7 +7,7 @@
// License: MIT
// *************************************************************
-namespace GraphQL.AspNet.Common.Extensions
+namespace GraphQL.AspNet.Common.JsonNodes
{
using System;
using System.Text.Json.Nodes;
diff --git a/src/graphql-aspnet/Common/JsonNodes/JsonNodeExtensions.cs b/src/graphql-aspnet/Common/JsonNodes/JsonNodeExtensions.cs
index bc0d74b20..be7adf724 100644
--- a/src/graphql-aspnet/Common/JsonNodes/JsonNodeExtensions.cs
+++ b/src/graphql-aspnet/Common/JsonNodes/JsonNodeExtensions.cs
@@ -7,7 +7,7 @@
// License: MIT
// *************************************************************
-namespace GraphQL.AspNet.Common.Extensions
+namespace GraphQL.AspNet.Common.JsonNodes
{
using System;
using System.Collections.Generic;
diff --git a/src/graphql-aspnet/Common/TypeCollection.cs b/src/graphql-aspnet/Common/TypeCollection.cs
deleted file mode 100644
index e2cf34b5d..000000000
--- a/src/graphql-aspnet/Common/TypeCollection.cs
+++ /dev/null
@@ -1,96 +0,0 @@
-// *************************************************************
-// project: graphql-aspnet
-// --
-// repo: https://github.com/graphql-aspnet
-// docs: https://graphql-aspnet.github.io
-// --
-// License: MIT
-// *************************************************************
-
-namespace GraphQL.AspNet.Common
-{
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Collections.Immutable;
-
- ///
- /// A collection of unique types. This collection is guaranteed to contain unique items
- /// and is read-only once created.
- ///
- public class TypeCollection : IEnumerable
- {
- ///
- /// Gets a collection representing no additional types.
- ///
- /// The none.
- public static TypeCollection Empty { get; }
-
- ///
- /// Initializes static members of the class.
- ///
- static TypeCollection()
- {
- Empty = new TypeCollection();
- }
-
- ///
- /// Prevents a default instance of the class from being created.
- ///
- private TypeCollection()
- {
- this.TypeSet = ImmutableHashSet.Create();
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The types.
- public TypeCollection(params Type[] types)
- : this()
- {
- if (types != null)
- this.TypeSet = ImmutableHashSet.Create(types);
- }
-
- ///
- /// Determines whether this instance contains the specified type.
- ///
- /// The type.
- /// true if the type is found; otherwise, false.
- public bool Contains(Type type)
- {
- return this.TypeSet.Contains(type);
- }
-
- ///
- /// Gets the count of types in this set.
- ///
- /// The count of types.
- public int Count => this.TypeSet.Count;
-
- ///
- /// Gets the set of s that represent the scalar.
- ///
- /// The type set.
- public IImmutableSet TypeSet { get; }
-
- ///
- /// Returns an enumerator that iterates through the collection.
- ///
- /// An enumerator that can be used to iterate through the collection.
- public IEnumerator GetEnumerator()
- {
- return this.TypeSet.GetEnumerator();
- }
-
- ///
- /// Returns an enumerator that iterates through a collection.
- ///
- /// An object that can be used to iterate through the collection.
- IEnumerator IEnumerable.GetEnumerator()
- {
- return this.GetEnumerator();
- }
- }
-}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/DirectiveBindingConfiguration.cs b/src/graphql-aspnet/Configuration/DirectiveBindingSchemaExtension.cs
similarity index 88%
rename from src/graphql-aspnet/Configuration/DirectiveBindingConfiguration.cs
rename to src/graphql-aspnet/Configuration/DirectiveBindingSchemaExtension.cs
index a52d1724b..dcd2786ac 100644
--- a/src/graphql-aspnet/Configuration/DirectiveBindingConfiguration.cs
+++ b/src/graphql-aspnet/Configuration/DirectiveBindingSchemaExtension.cs
@@ -21,7 +21,7 @@ namespace GraphQL.AspNet.Configuration
/// A configuration class used to apply a late-bound directive to a set of schema items
/// matching a filter.
///
- public sealed class DirectiveBindingConfiguration : ISchemaConfigurationExtension
+ public sealed class DirectiveBindingSchemaExtension : IGraphQLServerExtension
{
// a set of default filters applied to any directive applicator unless explicitly removed
// by the developer. Used to auto filter items down to those reasonably assumed
@@ -30,9 +30,9 @@ public sealed class DirectiveBindingConfiguration : ISchemaConfigurationExtensio
private static IReadOnlyList> _defaultFilters;
///
- /// Initializes static members of the class.
+ /// Initializes static members of the class.
///
- static DirectiveBindingConfiguration()
+ static DirectiveBindingSchemaExtension()
{
var list = new List>(4);
@@ -53,10 +53,10 @@ static DirectiveBindingConfiguration()
private List> _customFilters;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// Type of the directive being applied in this instnace.
- public DirectiveBindingConfiguration(Type directiveType)
+ public DirectiveBindingSchemaExtension(Type directiveType)
: this()
{
_directiveType = Validation.ThrowIfNullOrReturn(directiveType, nameof(directiveType));
@@ -64,24 +64,24 @@ public DirectiveBindingConfiguration(Type directiveType)
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// Name of the directive as it is declared in the schema
/// where it is being applied.
- public DirectiveBindingConfiguration(string directiveName)
+ public DirectiveBindingSchemaExtension(string directiveName)
: this()
{
_directiveName = Validation.ThrowIfNullWhiteSpaceOrReturn(directiveName, nameof(directiveName));
}
- private DirectiveBindingConfiguration()
+ private DirectiveBindingSchemaExtension()
{
_customFilters = new List>();
this.WithArguments();
}
///
- void ISchemaConfigurationExtension.Configure(ISchema schema)
+ void IGraphQLServerExtension.EnsureSchema(ISchema schema)
{
var allFilters = _defaultFilters.Concat(_customFilters).ToList();
foreach (var schemaItem in schema.AllSchemaItems(includeDirectives: false))
@@ -125,7 +125,7 @@ void ISchemaConfigurationExtension.Configure(ISchema schema)
/// The arguments to supply to the directive when its
/// executed.
/// IDirectiveInjector.
- public DirectiveBindingConfiguration WithArguments(params object[] arguments)
+ public DirectiveBindingSchemaExtension WithArguments(params object[] arguments)
{
arguments = arguments ?? new object[0];
_argumentFunction = x => arguments;
@@ -141,7 +141,7 @@ public DirectiveBindingConfiguration WithArguments(params object[] arguments)
/// A function that will be used to
/// create a new unique set of arguments per schema item the directive is applied to.
/// IDirectiveInjector.
- public DirectiveBindingConfiguration WithArguments(Func argsCreator)
+ public DirectiveBindingSchemaExtension WithArguments(Func argsCreator)
{
Validation.ThrowIfNull(argsCreator, nameof(argsCreator));
_argumentFunction = argsCreator;
@@ -158,7 +158,7 @@ public DirectiveBindingConfiguration WithArguments(Func a
///
/// The item filter.
/// DirectiveApplicator.
- public DirectiveBindingConfiguration ToItems(Func itemFilter)
+ public DirectiveBindingSchemaExtension ToItems(Func itemFilter)
{
Validation.ThrowIfNull(itemFilter, nameof(itemFilter));
_customFilters.Add(itemFilter);
@@ -169,7 +169,7 @@ public DirectiveBindingConfiguration ToItems(Func itemFilter)
/// Clears this instance of any directive arguments and filter criteria.
///
/// DirectiveApplicator.
- public DirectiveBindingConfiguration Clear()
+ public DirectiveBindingSchemaExtension Clear()
{
_customFilters.Clear();
this.WithArguments();
diff --git a/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs b/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs
index 6311ea0b9..50eb9537c 100644
--- a/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs
+++ b/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs
@@ -11,6 +11,7 @@ namespace GraphQL.AspNet.Configuration.Formatting
{
using System.Diagnostics;
using GraphQL.AspNet.Common.Extensions;
+ using GraphQL.AspNet.Schemas.TypeSystem.Scalars;
///
/// A formatter class capable of altering a graph item name before its added to a schema.
@@ -87,10 +88,6 @@ public virtual string FormatEnumValueName(string name)
/// System.String.
public virtual string FormatGraphTypeName(string name)
{
- // scalar names are considered fixed constants and can't be changed
- if (name == null || GraphQLProviders.ScalarProvider.IsScalar(name))
- return name;
-
return this.FormatName(name, _typeNameStrategy);
}
diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Directives.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Directives.cs
new file mode 100644
index 000000000..7ab773e64
--- /dev/null
+++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Directives.cs
@@ -0,0 +1,209 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using GraphQL.AspNet.Attributes;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Execution.Parsing.Lexing.Tokens;
+ using GraphQL.AspNet.Interfaces.Configuration;
+ using GraphQL.AspNet.Interfaces.Controllers;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions;
+ using GraphQL.AspNet.Schemas.TypeSystem;
+ using Microsoft.AspNetCore.Authorization;
+
+ ///
+ /// Extension methods for configuring minimal API methods as fields on the graph.
+ ///
+ public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions
+ {
+ ///
+ /// Adds policy-based authorization requirements to the directive.
+ ///
+ ///
+ /// This is similar to adding the to a controller method
+ ///
+ /// The directive being built.
+ /// The name of the policy to assign via this requirement.
+ /// A comma-seperated list of roles to assign via this requirement.
+ /// IGraphQLRuntimeDirectiveDefinition.
+ public static IGraphQLRuntimeDirectiveDefinition RequireAuthorization(
+ this IGraphQLRuntimeDirectiveDefinition directiveTemplate,
+ string policyName = null,
+ string roles = null)
+ {
+ Validation.ThrowIfNull(directiveTemplate, nameof(directiveTemplate));
+
+ return RequireAuthorizationInternal(directiveTemplate, policyName, roles);
+ }
+
+ ///
+ /// Indicates that the directive should allow anonymous access.
+ ///
+ ///
+ ///
+ /// This is similar to adding the to a controller method
+ ///
+ ///
+ /// Any inherited authorization permissions from field groups are automatically
+ /// dropped from this field instance.
+ ///
+ ///
+ /// The directive being built.
+ /// IGraphQLRuntimeDirectiveDefinition.
+ public static IGraphQLRuntimeDirectiveDefinition AllowAnonymous(this IGraphQLRuntimeDirectiveDefinition directiveTemplate)
+ {
+ Validation.ThrowIfNull(directiveTemplate, nameof(directiveTemplate));
+ return AllowAnonymousInternal(directiveTemplate);
+ }
+
+ ///
+ /// Marks this directive as being repeatable such that it can be applied to a single
+ /// schema item more than once.
+ ///
+ /// The directive template.
+ /// IGraphQLRuntimeDirectiveDefinition.
+ public static IGraphQLRuntimeDirectiveDefinition IsRepeatable(this IGraphQLRuntimeDirectiveDefinition directiveTemplate)
+ {
+ Validation.ThrowIfNull(directiveTemplate, nameof(directiveTemplate));
+ if (directiveTemplate.Attributes.Count(x => x is RepeatableAttribute) == 0)
+ {
+ var repeatable = new RepeatableAttribute();
+ directiveTemplate.AddAttribute(repeatable);
+ }
+
+ return directiveTemplate;
+ }
+
+ ///
+ /// Restricts the locations that this directive can be applied.
+ ///
+ ///
+ /// If called more than once this method acts as an additive restrictor. Each additional
+ /// call will add more location restrictions. Duplicate restrictions are ignored.
+ ///
+ /// The directive template to alter.
+ /// The bitwise set of locations where this
+ /// directive can be applied.
+ /// IGraphQLRuntimeDirectiveDefinition.
+ public static IGraphQLRuntimeDirectiveDefinition RestrictLocations(
+ this IGraphQLRuntimeDirectiveDefinition directiveTemplate,
+ DirectiveLocation locations)
+ {
+ Validation.ThrowIfNull(directiveTemplate, nameof(directiveTemplate));
+ var restrictions = new DirectiveLocationsAttribute(locations);
+ directiveTemplate.AddAttribute(restrictions);
+
+ return directiveTemplate;
+ }
+
+ ///
+ /// Sets the resolver to be used when this directive is requested at runtime.
+ ///
+ ///
+ ///
+ /// If this method is called more than once, the previously set resolver will be replaced.
+ ///
+ ///
+ /// Directive resolver methods must return a .
+ ///
+ ///
+ /// The directive being built.
+ /// The delegate to assign as the resolver. This method will be
+ /// parsed to determine input arguments for the field on the target schema.
+ /// IGraphQLRuntimeDirectiveDefinition.
+ public static IGraphQLRuntimeDirectiveDefinition AddResolver(this IGraphQLRuntimeDirectiveDefinition directiveTemplate, Delegate resolverMethod)
+ {
+ return AddResolverInternal(directiveTemplate, null, null, resolverMethod);
+ }
+
+ ///
+ /// Assigns a custom value to the internal name of this directive. This value will be used in error
+ /// messages and log entries instead of an anonymous method name. This can significantly increase readability
+ /// while trying to debug an issue.
+ ///
+ ///
+ /// This value does NOT affect the directive name as it would appear in a schema. It only effects the internal
+ /// name used in log messages and exception text.
+ ///
+ /// The directive being built.
+ /// The value to use as the internal name for this field definition when its
+ /// added to the schema.
+ /// IGraphQLRuntimeDirectiveDefinition.
+ public static IGraphQLRuntimeDirectiveDefinition WithInternalName(this IGraphQLRuntimeDirectiveDefinition directiveTemplate, string internalName)
+ {
+ Validation.ThrowIfNull(directiveTemplate, nameof(directiveTemplate));
+ directiveTemplate.InternalName = internalName;
+ return directiveTemplate;
+ }
+
+ ///
+ /// Maps a new directive into the target schema.
+ ///
+ /// The builder representing the schema being constructed.
+ /// Name of the directive (e.g. '@myDirective').
+ /// IGraphQLRuntimeDirectiveDefinition.
+ public static IGraphQLRuntimeDirectiveDefinition MapDirective(this ISchemaBuilder schemaBuilder, string directiveName)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+ return MapDirective(schemaBuilder.Options, directiveName, null as Delegate);
+ }
+
+ ///
+ /// Maps a new directive into the target schema.
+ ///
+ /// The builder representing the schema being constructed.
+ /// Name of the directive (e.g. '@myDirective').
+ /// The resolver that will be executed when the directive is invoked.
+ /// IGraphQLDirectiveTemplate.
+ public static IGraphQLRuntimeDirectiveDefinition MapDirective(this ISchemaBuilder schemaBuilder, string directiveName, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+ return MapDirective(schemaBuilder.Options, directiveName, resolverMethod);
+ }
+
+ ///
+ /// Maps a new directive into the target schema.
+ ///
+ /// The schema options where the directive will be created.
+ /// Name of the directive (e.g. '@myDirective').
+ /// IGraphQLRuntimeDirectiveDefinition.
+ public static IGraphQLRuntimeDirectiveDefinition MapDirective(this SchemaOptions schemaOptions, string directiveName)
+ {
+ return MapDirective(schemaOptions, directiveName, null as Delegate);
+ }
+
+ ///
+ /// Maps a new directive into the target schema.
+ ///
+ /// The schema options where the directive will be created.
+ /// Name of the directive (e.g. '@myDirective').
+ /// The resolver that will be executed when the directive is invoked.
+ /// IGraphQLRuntimeDirectiveDefinition.
+ public static IGraphQLRuntimeDirectiveDefinition MapDirective(this SchemaOptions schemaOptions, string directiveName, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(schemaOptions, nameof(schemaOptions));
+
+ while (directiveName != null && directiveName.StartsWith(TokenTypeNames.STRING_AT_SYMBOL))
+ directiveName = directiveName.Substring(1);
+
+ var directive = new RuntimeDirectiveActionDefinition(schemaOptions, directiveName);
+ schemaOptions.AddRuntimeSchemaItem(directive);
+
+ if (resolverMethod != null)
+ directive.AddResolver(resolverMethod);
+
+ return directive;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_FieldGroups.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_FieldGroups.cs
new file mode 100644
index 000000000..78a0b16b3
--- /dev/null
+++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_FieldGroups.cs
@@ -0,0 +1,154 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using System;
+ using System.Linq;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Interfaces.Controllers;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using Microsoft.AspNetCore.Authorization;
+
+ ///
+ /// Extension methods for configuring minimal API methods as fields on the graph.
+ ///
+ public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions
+ {
+ ///
+ /// Adds policy-based authorization requirements to the field.
+ ///
+ ///
+ /// This is similar to adding the to a controller method. Subsequent calls to this
+ /// method will cause more authorization restrictions to be added to the field.
+ ///
+ /// The field being built.
+ /// The name of the policy to assign via this requirement.
+ /// A comma-seperated list of roles to assign via this requirement.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeFieldGroupDefinition RequireAuthorization(
+ this IGraphQLRuntimeFieldGroupDefinition fieldBuilder,
+ string policyName = null,
+ string roles = null)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+
+ var attrib = new AuthorizeAttribute();
+ attrib.Policy = policyName?.Trim();
+ attrib.Roles = roles?.Trim();
+ fieldBuilder.AddAttribute(attrib);
+ return fieldBuilder;
+ }
+
+ ///
+ /// Indicates that the field should allow anonymous access.
+ ///
+ ///
+ ///
+ /// This is similar to adding the to a controller method
+ ///
+ ///
+ /// Any inherited authorization permissions from field groups are automatically
+ /// dropped from this field instance.
+ ///
+ ///
+ /// The field being built.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeFieldGroupDefinition AllowAnonymous(this IGraphQLRuntimeFieldGroupDefinition fieldBuilder)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ if (!fieldBuilder.Attributes.OfType().Any())
+ fieldBuilder.AddAttribute(new AllowAnonymousAttribute());
+
+ return fieldBuilder;
+ }
+
+ ///
+ /// Maps a terminal child field into the schema and assigns the resolver method to it.
+ ///
+ /// The field under which this new field will be nested.
+ /// The template pattern to be appended to the supplied .
+ /// IGraphQLResolvedFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapField(this IGraphQLRuntimeFieldGroupDefinition field, string subTemplate)
+ {
+ return MapField(
+ field,
+ subTemplate,
+ null as Type, // expectedReturnType
+ null, // unionName
+ null as Delegate);
+ }
+
+ ///
+ /// Maps a terminal child field into the schema and assigns the resolver method to it.
+ ///
+ /// The field under which this new field will be nested.
+ /// The template pattern to be appended to the supplied .
+ /// The resolver method to be called when this field is requested.
+ /// IGraphQLResolvedFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapField(this IGraphQLRuntimeFieldGroupDefinition field, string subTemplate, Delegate resolverMethod)
+ {
+ return MapField(
+ field,
+ subTemplate,
+ null as Type, // expectedReturnType
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Maps a terminal child field into the schema and assigns the resolver method to it.
+ ///
+ /// The expected, primary return type of the field. Must be provided
+ /// if the supplied delegate returns an .
+ /// The field under which this new field will be nested.
+ /// The template pattern to be appended to the supplied .
+ /// The resolver method to be called when this field is requested.
+ /// IGraphQLResolvedFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapField(this IGraphQLRuntimeFieldGroupDefinition field, string subTemplate, Delegate resolverMethod)
+ {
+ return MapField(
+ field,
+ subTemplate,
+ typeof(TReturnType), // expectedReturnType
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Maps a terminal child field into the schema and assigns the resolver method to it.
+ ///
+ /// The field under which this new field will be nested.
+ /// The template pattern to be appended to the supplied .
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The resolver method to be called when this field is requested.
+ /// IGraphQLResolvedFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapField(this IGraphQLRuntimeFieldGroupDefinition field, string subTemplate, string unionName, Delegate resolverMethod)
+ {
+ return MapField(
+ field,
+ subTemplate,
+ null as Type, // expectedReturnType
+ unionName, // unionName
+ resolverMethod);
+ }
+
+ private static IGraphQLRuntimeResolvedFieldDefinition MapField(
+ IGraphQLRuntimeFieldGroupDefinition field,
+ string subTemplate,
+ Type expectedReturnType,
+ string unionName,
+ Delegate resolverMethod)
+ {
+ var subField = field.MapField(subTemplate);
+ AddResolverInternal(subField, expectedReturnType, unionName, resolverMethod);
+ return subField;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Internals.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Internals.cs
new file mode 100644
index 000000000..43264c1e1
--- /dev/null
+++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Internals.cs
@@ -0,0 +1,129 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using System;
+ using System.Linq;
+ using GraphQL.AspNet.Attributes;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Execution;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions;
+ using GraphQL.AspNet.Schemas.TypeSystem;
+ using Microsoft.AspNetCore.Authorization;
+
+ ///
+ /// Extension methods for configuring minimal API methods as fields on the graph.
+ ///
+ public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions
+ {
+ private static TItemType RequireAuthorizationInternal(
+ this TItemType schemaItem,
+ string policyName = null,
+ string roles = null)
+ where TItemType : IGraphQLRuntimeSchemaItemDefinition
+ {
+ Validation.ThrowIfNull(schemaItem, nameof(schemaItem));
+
+ var attrib = new AuthorizeAttribute();
+ attrib.Policy = policyName?.Trim();
+ attrib.Roles = roles?.Trim();
+ schemaItem.AddAttribute(attrib);
+
+ // remove any allow anon attriubtes
+ var allowAnonAttribs = schemaItem.Attributes.OfType().ToList();
+ foreach (var anonAttrib in allowAnonAttribs)
+ schemaItem.RemoveAttribute(anonAttrib);
+
+ return schemaItem;
+ }
+
+ private static TItemType AllowAnonymousInternal(TItemType schemaItem)
+ where TItemType : IGraphQLRuntimeSchemaItemDefinition
+ {
+ Validation.ThrowIfNull(schemaItem, nameof(schemaItem));
+ if (schemaItem.Attributes.Count(x => x is AllowAnonymousAttribute) == 0)
+ {
+ schemaItem.AddAttribute(new AllowAnonymousAttribute());
+ }
+
+ // remove any authorize attributes
+ var authAttribs = schemaItem.Attributes.OfType().ToList();
+ foreach (var attib in authAttribs)
+ schemaItem.RemoveAttribute(attib);
+
+ return schemaItem;
+ }
+
+ private static TItemType ClearPossibleTypesInternal(TItemType fieldBuilder)
+ where TItemType : IGraphQLRuntimeSchemaItemDefinition
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ var attributes = fieldBuilder.Attributes.OfType().ToList();
+ foreach (var att in attributes)
+ fieldBuilder.RemoveAttribute(att);
+
+ return fieldBuilder;
+ }
+
+ private static TItemType AddPossibleTypesInternal(TItemType fieldBuilder, Type firstPossibleType, params Type[] additionalPossibleTypes)
+ where TItemType : IGraphQLRuntimeSchemaItemDefinition
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ var possibleTypes = new PossibleTypesAttribute(firstPossibleType, additionalPossibleTypes);
+ fieldBuilder.AddAttribute(possibleTypes);
+ return fieldBuilder;
+ }
+
+ private static IGraphQLRuntimeFieldGroupDefinition MapGraphQLFieldInternal(
+ SchemaOptions schemaOptions,
+ GraphOperationType operationType,
+ string pathTemplate)
+ {
+ schemaOptions = Validation.ThrowIfNullOrReturn(schemaOptions, nameof(schemaOptions));
+ pathTemplate = Validation.ThrowIfNullWhiteSpaceOrReturn(pathTemplate, nameof(pathTemplate));
+
+ var fieldTemplate = new RuntimeFieldGroupTemplate(
+ schemaOptions,
+ (SchemaItemCollections)operationType,
+ pathTemplate);
+
+ return fieldTemplate;
+ }
+
+ private static TItemType AddResolverInternal(this TItemType fieldBuilder, Type expectedReturnType, string unionName, Delegate resolverMethod)
+ where TItemType : IGraphQLResolvableSchemaItemDefinition
+ {
+ fieldBuilder.Resolver = resolverMethod;
+ fieldBuilder.ReturnType = expectedReturnType;
+
+ // since the resolver was declared as non-union, remove any potential union setup that might have
+ // existed via a previous call. if TReturnType is a union proxy it will be
+ // picked up automatically during templating
+ var unionAttrib = fieldBuilder.Attributes.OfType().SingleOrDefault();
+ if (string.IsNullOrEmpty(unionName))
+ {
+ if (unionAttrib != null)
+ fieldBuilder.RemoveAttribute(unionAttrib);
+ }
+ else if (unionAttrib != null)
+ {
+ unionAttrib.UnionName = unionName?.Trim();
+ unionAttrib.UnionMemberTypes.Clear();
+ }
+ else
+ {
+ fieldBuilder.AddAttribute(new UnionAttribute(unionName.Trim()));
+ }
+
+ return fieldBuilder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Mutations.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Mutations.cs
new file mode 100644
index 000000000..4e24c694b
--- /dev/null
+++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Mutations.cs
@@ -0,0 +1,174 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using System;
+ using GraphQL.AspNet.Attributes;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Execution;
+ using GraphQL.AspNet.Interfaces.Configuration;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions;
+ using GraphQL.AspNet.Schemas.TypeSystem;
+
+ ///
+ /// Extension methods for configuring minimal API methods as fields on the graph.
+ ///
+ public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions
+ {
+ ///
+ /// Begins a new field group for the mutation schema object. All fields created using this group will be nested underneath it and inherit any set parameters such as authorization requirements.
+ ///
+ /// The builder to append the mutation group to.
+ /// The template path for this group.
+ /// IGraphQLRuntimeFieldDefinition.
+ public static IGraphQLRuntimeFieldGroupDefinition MapMutationGroup(this ISchemaBuilder schemaBuilder, string template)
+ {
+ return MapMutationGroup(schemaBuilder?.Options, template);
+ }
+
+ ///
+ /// Begins a new field group for the mutation schema object. All fields created using this group will be nested underneath it and inherit any set parameters such as authorization requirements.
+ ///
+ /// The schema options to append the mutation group to.
+ /// The template path for this group.
+ /// IGraphQLRuntimeFieldDefinition.
+ public static IGraphQLRuntimeFieldGroupDefinition MapMutationGroup(this SchemaOptions schemaOptions, string template)
+ {
+ return new RuntimeFieldGroupTemplate(schemaOptions, SchemaItemCollections.Mutation, template);
+ }
+
+ ///
+ /// Creates a new, explicitly resolvable field in the mutation root object with the given path. This field cannot be
+ /// further extended or nested with other fields via the Mapping API.
+ ///
+ /// The builder representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this ISchemaBuilder schemaBuilder, string template)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+
+ return MapMutation(
+ schemaBuilder.Options,
+ template,
+ null, // unionName
+ null as Delegate);
+ }
+
+ ///
+ /// Creates a new field in the mutation object with the given path. This field can act as a
+ /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself.
+ ///
+ /// The builder representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// The resolver method to execute when this
+ /// field is requested.
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this ISchemaBuilder schemaBuilder, string template, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+ return MapMutation(
+ schemaBuilder.Options,
+ template,
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Creates a new field in the mutation object with the given path. This field can act as a
+ /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself.
+ ///
+ /// The builder representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The resolver method to execute when this
+ /// field is requested.
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this ISchemaBuilder schemaBuilder, string template, string unionName, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+
+ return MapMutation(
+ schemaBuilder.Options,
+ template,
+ unionName,
+ resolverMethod);
+ }
+
+ ///
+ /// Creates a new field in the mutation root object with the given path. This field can act as a
+ /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself.
+ ///
+ /// The options representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this SchemaOptions schemaOptions, string template)
+ {
+ return MapMutation(
+ schemaOptions,
+ template,
+ null, // unionName
+ null as Delegate);
+ }
+
+ ///
+ /// Creates a new, explicitly resolvable field in the Mutation root object with the given path. This field cannot be
+ /// further extended or nested with other fields via the Mapping API.
+ ///
+ /// The options representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// The resolver method to execute when
+ /// this field is requested by a caller.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this SchemaOptions schemaOptions, string template, Delegate resolverMethod)
+ {
+ return MapMutation(
+ schemaOptions,
+ template,
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Creates a new, explicitly resolvable field in the Mutation root object with the given path. This field cannot be
+ /// further extended or nested with other fields via the Mapping API.
+ ///
+ /// The options representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The resolver method to execute when
+ /// this field is requested by a caller.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this SchemaOptions schemaOptions, string template, string unionName, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(schemaOptions, nameof(schemaOptions));
+
+ var field = MapGraphQLFieldInternal(
+ schemaOptions,
+ GraphOperationType.Mutation,
+ template);
+
+ var resolvedField = RuntimeResolvedFieldDefinition.FromFieldTemplate(field);
+ schemaOptions.AddRuntimeSchemaItem(resolvedField);
+
+ if (!string.IsNullOrWhiteSpace(unionName))
+ resolvedField.AddAttribute(new UnionAttribute(unionName.Trim()));
+
+ return resolvedField.AddResolver(unionName, resolverMethod);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Queries.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Queries.cs
new file mode 100644
index 000000000..af02bcd82
--- /dev/null
+++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Queries.cs
@@ -0,0 +1,176 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using System;
+ using GraphQL.AspNet.Attributes;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Execution;
+ using GraphQL.AspNet.Interfaces.Configuration;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions;
+ using GraphQL.AspNet.Schemas.TypeSystem;
+
+ ///
+ /// Extension methods for configuring minimal API methods as fields on the graph.
+ ///
+ public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions
+ {
+ ///
+ /// Begins a new field group for the query schema object. All fields created using
+ /// this group will be nested underneath it and inherit any set parameters such as authorization requirements.
+ ///
+ /// The builder to append the query group to.
+ /// The template path for this group.
+ /// IGraphQLRuntimeFieldDefinition.
+ public static IGraphQLRuntimeFieldGroupDefinition MapQueryGroup(this ISchemaBuilder schemaBuilder, string template)
+ {
+ return MapQueryGroup(schemaBuilder?.Options, template);
+ }
+
+ ///
+ /// Begins a new field group for the query schema object. All fields created using
+ /// this group will be nested underneath it and inherit any set parameters such as authorization requirements.
+ ///
+ /// The schema options to append the query group to.
+ /// The template path for this group.
+ /// IGraphQLRuntimeFieldDefinition.
+ public static IGraphQLRuntimeFieldGroupDefinition MapQueryGroup(this SchemaOptions schemaOptions, string template)
+ {
+ return new RuntimeFieldGroupTemplate(schemaOptions, SchemaItemCollections.Query, template);
+ }
+
+ ///
+ /// Creates a new, explicitly resolvable field in the query root object with the given path. This field cannot be
+ /// further extended or nested with other fields via the Mapping API.
+ ///
+ /// The builder representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this ISchemaBuilder schemaBuilder, string template)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+
+ return MapQuery(
+ schemaBuilder.Options,
+ template,
+ null, // unionName
+ null as Delegate);
+ }
+
+ ///
+ /// Creates a new field in the query object with the given path. This field can act as a
+ /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself.
+ ///
+ /// The builder representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// The resolver method to execute when this
+ /// field is requested.
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this ISchemaBuilder schemaBuilder, string template, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+ return MapQuery(
+ schemaBuilder.Options,
+ template,
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Creates a new field in the query object with the given path. This field can act as a
+ /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself.
+ ///
+ /// The builder representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The resolver method to execute when this
+ /// field is requested.
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this ISchemaBuilder schemaBuilder, string template, string unionName, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder));
+
+ return MapQuery(
+ schemaBuilder.Options,
+ template,
+ unionName,
+ resolverMethod);
+ }
+
+ ///
+ /// Creates a new field in the query root object with the given path. This field can act as a
+ /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself.
+ ///
+ /// The options representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// IGraphQLFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this SchemaOptions schemaOptions, string template)
+ {
+ return MapQuery(
+ schemaOptions,
+ template,
+ null, // unionMethod
+ null as Delegate);
+ }
+
+ ///
+ /// Creates a new, explicitly resolvable field in the query root object with the given path. This field cannot be
+ /// further extended or nested with other fields via the Mapping API.
+ ///
+ /// The options representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// The resolver method to execute when
+ /// this field is requested by a caller.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this SchemaOptions schemaOptions, string template, Delegate resolverMethod)
+ {
+ return MapQuery(
+ schemaOptions,
+ template,
+ null, // unionMethod
+ resolverMethod);
+ }
+
+ ///
+ /// Creates a new, explicitly resolvable field in the query root object with the given path. This field cannot be
+ /// further extended or nested with other fields via the Mapping API.
+ ///
+ /// The options representing the schema where this field
+ /// will be created.
+ /// The template path string for his field. (e.g. /path1/path2/path3)
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The resolver method to execute when
+ /// this field is requested by a caller.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this SchemaOptions schemaOptions, string template, string unionName, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(schemaOptions, nameof(schemaOptions));
+
+ var field = MapGraphQLFieldInternal(
+ schemaOptions,
+ GraphOperationType.Query,
+ template);
+
+ var resolvedField = RuntimeResolvedFieldDefinition.FromFieldTemplate(field);
+ schemaOptions.AddRuntimeSchemaItem(resolvedField);
+
+ if (!string.IsNullOrWhiteSpace(unionName))
+ resolvedField.AddAttribute(new UnionAttribute(unionName.Trim()));
+
+ return resolvedField.AddResolver(unionName, resolverMethod);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_ResolvedFields.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_ResolvedFields.cs
new file mode 100644
index 000000000..c7eb91ed2
--- /dev/null
+++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_ResolvedFields.cs
@@ -0,0 +1,177 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using System;
+ using System.Linq;
+ using System.Reflection;
+ using GraphQL.AspNet.Attributes;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Interfaces.Controllers;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using Microsoft.AspNetCore.Authorization;
+
+ ///
+ /// Extension methods for configuring minimal API methods as fields on the graph.
+ ///
+ public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions
+ {
+ ///
+ /// Adds policy-based authorization requirements to the field.
+ ///
+ ///
+ /// This is similar to adding the to a controller method
+ ///
+ /// The field being built.
+ /// The name of the policy to assign via this requirement.
+ /// A comma-seperated list of roles to assign via this requirement.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition RequireAuthorization(
+ this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder,
+ string policyName = null,
+ string roles = null)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ return RequireAuthorizationInternal(fieldBuilder, policyName, roles);
+ }
+
+ ///
+ /// Indicates that the field should allow anonymous access. This will override any potential authorization requirements setup via
+ /// the "MapGroup" methods if this field was created within a group.
+ ///
+ ///
+ /// This is similar to adding the to a controller method
+ ///
+ /// The field being built.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition AllowAnonymous(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ return AllowAnonymousInternal(fieldBuilder);
+ }
+
+ ///
+ /// Adds a set of possible return types for this field. This is synonymous to using the
+ /// on a controller's action method.
+ ///
+ ///
+ /// This method can be called multiple times. Any new types will be appended to the field. All types added
+ /// must be coercable to the declared return type of the assigned resolver for this field unless this field returns a union; in
+ /// which case the types will be added as union members.
+ ///
+ /// The field being built.
+ /// The first possible type that might be returned by this
+ /// field.
+ /// Any number of additional possible types that
+ /// might be returned by this field.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition AddPossibleTypes(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder, Type firstPossibleType, params Type[] additionalPossibleTypes)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ return AddPossibleTypesInternal(fieldBuilder, firstPossibleType, additionalPossibleTypes);
+ }
+
+ ///
+ /// Clears all extra defined possible types this field may declare. This will not affect the core type defined by the resolver, if
+ /// a resolver has been defined for this field.
+ ///
+ /// The field builder.
+ /// IGraphQLRuntimeResolvedFieldDefinition.
+ public static IGraphQLRuntimeResolvedFieldDefinition ClearPossibleTypes(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ return ClearPossibleTypesInternal(fieldBuilder);
+ }
+
+ ///
+ /// Assigns a custom internal name to this field. This value will be used in error
+ /// messages and log entries instead of an anonymous method name. This can significantly increase readability
+ /// while trying to debug an issue. This value has no bearing on the runtime use of this field. It is cosmetic only.
+ ///
+ ///
+ /// This value does NOT affect the field name as it would appear in a schema. It only effects the internal
+ /// name used in log messages and exception text.
+ ///
+ /// The field being built.
+ /// The value to use as the internal name for this field definition when its
+ /// added to the schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition WithInternalName(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder, string internalName)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ fieldBuilder.InternalName = internalName?.Trim();
+ return fieldBuilder;
+ }
+
+ ///
+ /// Indicates this field will return a union and sets the resolver to be used when this field is requested at runtime. The provided
+ /// resolver should return a .
+ ///
+ /// The field being built.
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The delegate to assign as the resolver. This method will be
+ /// parsed to determine input arguments for the field on the target schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition AddResolver(
+ this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder,
+ string unionName,
+ Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ return AddResolverInternal(
+ fieldBuilder,
+ null as Type, // return Type
+ unionName,
+ resolverMethod);
+ }
+
+ ///
+ /// Sets the resolver to be used when this field is requested at runtime.
+ ///
+ ///
+ /// If this method is called more than once the previously set resolver will be replaced.
+ ///
+ /// The field being built.
+ /// The delegate to assign as the resolver. This method will be
+ /// parsed to determine input arguments for the field on the target schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition AddResolver(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ return AddResolverInternal(
+ fieldBuilder,
+ null as Type, // return Type
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Sets the resolver to be used when this field is requested at runtime.
+ ///
+ ///
+ /// If this method is called more than once the previously set resolver will be replaced.
+ ///
+ /// The expected, primary return type of the field. Must be provided
+ /// if the supplied delegate returns an .
+ /// The field being built.
+ /// The delegate to assign as the resolver. This method will be
+ /// parsed to determine input arguments for the field on the target schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeResolvedFieldDefinition AddResolver(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ return AddResolverInternal(
+ fieldBuilder,
+ typeof(TReturnType),
+ null, // unionName
+ resolverMethod);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_TypeExtensions.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_TypeExtensions.cs
new file mode 100644
index 000000000..accaf668a
--- /dev/null
+++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_TypeExtensions.cs
@@ -0,0 +1,437 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using System;
+ using System.Collections.Generic;
+ using GraphQL.AspNet.Attributes;
+ using GraphQL.AspNet.Common;
+ using GraphQL.AspNet.Execution;
+ using GraphQL.AspNet.Interfaces.Configuration;
+ using GraphQL.AspNet.Interfaces.Controllers;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
+ using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions;
+ using Microsoft.AspNetCore.Authorization;
+
+ ///
+ /// Extension methods for configuring minimal API methods as fields on the graph.
+ ///
+ public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions
+ {
+ ///
+ /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema.
+ ///
+ ///
+ ///
+ /// This method is synonymous with using the on
+ /// a controller action.
+ ///
+ ///
+ /// The supplied resolver must declare a parameter that is of the same type as .
+ ///
+ ///
+ /// The concrete interface, class or struct to extend with a new field.
+ /// The schema builder to append the field to.
+ /// Name of the field to add to the .
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this ISchemaBuilder builder, string fieldName)
+ {
+ Validation.ThrowIfNull(builder, nameof(builder));
+
+ return MapTypeExtension(
+ builder.Options,
+ typeof(TOwnerType),
+ fieldName,
+ null, // unionName
+ null as Delegate);
+ }
+
+ ///
+ /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema.
+ ///
+ ///
+ ///
+ /// This method is synonymous with using the on
+ /// a controller action.
+ ///
+ ///
+ /// The supplied resolver must declare a parameter that is of the same type as .
+ ///
+ ///
+ /// The concrete interface, class or struct to extend with a new field.
+ /// The schema builder to append the field to.
+ /// Name of the field to add to the .
+ /// The resolver method to be called when the field is requested.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this ISchemaBuilder builder, string fieldName, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(builder, nameof(builder));
+
+ return MapTypeExtension(
+ builder.Options,
+ typeof(TOwnerType),
+ fieldName,
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema.
+ ///
+ ///
+ ///
+ /// This method is synonymous with using the on
+ /// a controller action.
+ ///
+ ///
+ /// The supplied resolver must declare a parameter that is of the same type as .
+ ///
+ ///
+ /// The concrete interface, class or struct to extend with a new field.
+ /// The schema builder to append the field to.
+ /// Name of the field to add to the .
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The resolver method to be called when the field is requested.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this ISchemaBuilder builder, string fieldName, string unionName, Delegate resolverMethod)
+ {
+ Validation.ThrowIfNull(builder, nameof(builder));
+
+ return MapTypeExtension(
+ builder.Options,
+ typeof(TOwnerType),
+ fieldName,
+ unionName,
+ resolverMethod);
+ }
+
+ ///
+ /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema.
+ ///
+ ///
+ ///
+ /// This method is synonymous with using the on
+ /// a controller action.
+ ///
+ ///
+ /// The supplied resolver must declare a parameter that is of the same type as .
+ ///
+ ///
+ /// The concrete interface, class or struct to extend with a new field.
+ /// The configuration options for the target schema.
+ /// Name of the field to add to the .
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, string fieldName)
+ {
+ return MapTypeExtension(
+ schemaOptions,
+ typeof(TOwnerType),
+ fieldName,
+ null, // unionName
+ null as Delegate);
+ }
+
+ ///
+ /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema.
+ ///
+ ///
+ ///
+ /// This method is synonymous with using the on
+ /// a controller action.
+ ///
+ ///
+ /// The supplied resolver must declare a parameter that is of the same type as .
+ ///
+ ///
+ /// The concrete interface, class or struct to extend with a new field.
+ /// The configuration options for the target schema.
+ /// Name of the field to add to the .
+ /// The resolver method to be called when the field is requested.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, string fieldName, Delegate resolverMethod)
+ {
+ return MapTypeExtension(
+ schemaOptions,
+ typeof(TOwnerType),
+ fieldName,
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Registers a new field to a given type on the target schema.
+ ///
+ ///
+ ///
+ /// The supplied resolver must declare a parameter that is of the same type as .
+ ///
+ ///
+ /// The configuration options for the target schema.
+ /// The concrete interface, class or struct to extend with a new field.
+ /// Name of the field to add to the .
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, Type fieldOwnerType, string fieldName)
+ {
+ return MapTypeExtension(
+ schemaOptions,
+ fieldOwnerType,
+ fieldName,
+ null, // unionName
+ null as Delegate);
+ }
+
+ ///
+ /// Registers a new field to a given type on the target schema.
+ ///
+ ///
+ ///
+ /// The supplied resolver must declare a parameter that is of the same type as .
+ ///
+ ///
+ /// The configuration options for the target schema.
+ /// The concrete interface, class or struct to extend with a new field.
+ /// Name of the field to add to the .
+ /// The resolver method to be called when the field is requested.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, Type fieldOwnerType, string fieldName, Delegate resolverMethod)
+ {
+ return MapTypeExtension(
+ schemaOptions,
+ fieldOwnerType,
+ fieldName,
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema.
+ ///
+ ///
+ ///
+ /// This method is synonymous with using the on
+ /// a controller action.
+ ///
+ ///
+ /// The supplied resolver must declare a parameter that is of the same type as .
+ ///
+ ///
+ /// The concrete interface, class or struct to extend with a new field.
+ /// The configuration options for the target schema.
+ /// Name of the field to add to the .
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The resolver method to be called when the field is requested.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, string fieldName, string unionName, Delegate resolverMethod)
+ {
+ return MapTypeExtension(
+ schemaOptions,
+ typeof(TOwnerType),
+ fieldName,
+ unionName,
+ resolverMethod);
+ }
+
+ ///
+ /// Registers a new field to a given type on the target schema.
+ ///
+ ///
+ ///
+ /// The supplied resolver must declare a parameter that is of the same type as .
+ ///
+ ///
+ /// The configuration options for the target schema.
+ /// The concrete interface, class or struct to extend with a new field.
+ /// Name of the field to add to the .
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The resolver method to be called when the field is requested.
+ /// IGraphQLResolvedFieldTemplate.
+ public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, Type fieldOwnerType, string fieldName, string unionName, Delegate resolverMethod)
+ {
+ schemaOptions = Validation.ThrowIfNullOrReturn(schemaOptions, nameof(schemaOptions));
+ fieldOwnerType = Validation.ThrowIfNullOrReturn(fieldOwnerType, nameof(fieldOwnerType));
+ fieldName = Validation.ThrowIfNullWhiteSpaceOrReturn(fieldName, nameof(fieldName));
+
+ IGraphQLRuntimeTypeExtensionDefinition field = new RuntimeTypeExtensionDefinition(
+ schemaOptions,
+ fieldOwnerType,
+ fieldName,
+ FieldResolutionMode.PerSourceItem);
+
+ schemaOptions.AddRuntimeSchemaItem(field);
+
+ if (resolverMethod != null)
+ field = field.AddResolver(unionName, resolverMethod);
+
+ return field;
+ }
+
+ ///
+ /// Instructs the new type extension field that it should process data in batched mode rather than
+ /// in a "per source item" mode.
+ ///
+ ///
+ ///
+ /// The supplied resolver must declare a parameter that is an of the same as
+ /// class, interface or struct that was originally extended as indicated by .
+ ///
+ ///
+ /// The type extension to make into a batch field.
+ /// IGraphQLTypeExtensionTemplate.
+ public static IGraphQLRuntimeTypeExtensionDefinition WithBatchProcessing(this IGraphQLRuntimeTypeExtensionDefinition typeExtension)
+ {
+ Validation.ThrowIfNull(typeExtension, nameof(typeExtension));
+ typeExtension.ExecutionMode = FieldResolutionMode.Batch;
+ return typeExtension;
+ }
+
+ ///
+ /// Adds a set of possible return types for this field. This is synonymous to using the
+ /// on a controller's action method.
+ ///
+ ///
+ /// This method can be called multiple times. Any new types will be appended to the field.
+ ///
+ /// The field being built.
+ /// The first possible type that might be returned by this
+ /// field.
+ /// Any number of additional possible types that
+ /// might be returned by this field.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeTypeExtensionDefinition AddPossibleTypes(this IGraphQLRuntimeTypeExtensionDefinition typeExtension, Type firstPossibleType, params Type[] additionalPossibleTypes)
+ {
+ Validation.ThrowIfNull(typeExtension, nameof(typeExtension));
+ return AddPossibleTypesInternal(typeExtension, firstPossibleType, additionalPossibleTypes);
+ }
+
+ ///
+ /// Clears all extra defined possible types this field may declare. This will not affect the core type defined by the resolver, if
+ /// a resolver has been defined for this field.
+ ///
+ /// The field builder.
+ /// IGraphQLRuntimeResolvedFieldDefinition.
+ public static IGraphQLRuntimeTypeExtensionDefinition ClearPossibleTypes(this IGraphQLRuntimeTypeExtensionDefinition typeExtension)
+ {
+ Validation.ThrowIfNull(typeExtension, nameof(typeExtension));
+ return ClearPossibleTypesInternal(typeExtension);
+ }
+
+ ///
+ /// Assigns a custom value to the internal name of this type exension. This value will be used in error
+ /// messages and log entries instead of an anonymous method name. This can significantly increase readability
+ /// while trying to debug an issue.
+ ///
+ ///
+ /// This value does NOT affect the field name as it would appear in a schema. It only effects the internal
+ /// name used in log messages and exception text.
+ ///
+ /// The type exension field being built.
+ /// The value to use as the internal name for this field definition when its
+ /// added to the schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeTypeExtensionDefinition WithInternalName(this IGraphQLRuntimeTypeExtensionDefinition typeExtension, string internalName)
+ {
+ typeExtension.InternalName = internalName;
+ return typeExtension;
+ }
+
+ ///
+ /// Adds policy-based authorization requirements to the field.
+ ///
+ ///
+ /// This is similar to adding the to a controller method
+ ///
+ /// The field being built.
+ /// The name of the policy to assign via this requirement.
+ /// A comma-seperated list of roles to assign via this requirement.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeTypeExtensionDefinition RequireAuthorization(
+ this IGraphQLRuntimeTypeExtensionDefinition fieldBuilder,
+ string policyName = null,
+ string roles = null)
+ {
+ Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder));
+ return RequireAuthorizationInternal(fieldBuilder, policyName, roles);
+ }
+
+ ///
+ /// Indicates that the field should allow anonymous access.
+ ///
+ ///
+ /// This is similar to adding the to a controller method
+ ///
+ /// The field being built.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeTypeExtensionDefinition AllowAnonymous(this IGraphQLRuntimeTypeExtensionDefinition typeExtension)
+ {
+ Validation.ThrowIfNull(typeExtension, nameof(typeExtension));
+ return AllowAnonymousInternal(typeExtension);
+ }
+
+ ///
+ /// Sets the resolver to be used when this field is requested at runtime.
+ ///
+ ///
+ /// If this method is called more than once the previously set resolver will be replaced.
+ ///
+ /// The field being built.
+ /// The delegate to assign as the resolver. This method will be
+ /// parsed to determine input arguments for the field on the target schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeTypeExtensionDefinition AddResolver(this IGraphQLRuntimeTypeExtensionDefinition typeExtension, Delegate resolverMethod)
+ {
+ return AddResolverInternal(
+ typeExtension,
+ null as Type, // returnType
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Sets the resolver to be used when this field is requested at runtime.
+ ///
+ ///
+ /// If this method is called more than once the previously set resolver will be replaced.
+ ///
+ /// The expected, primary return type of the field. Must be provided
+ /// if the supplied delegate returns an .
+ /// The field being built.
+ /// The delegate to assign as the resolver. This method will be
+ /// parsed to determine input arguments for the field on the target schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeTypeExtensionDefinition AddResolver(this IGraphQLRuntimeTypeExtensionDefinition fieldBuilder, Delegate resolverMethod)
+ {
+ return AddResolverInternal(
+ fieldBuilder,
+ typeof(TReturnType),
+ null, // unionName
+ resolverMethod);
+ }
+
+ ///
+ /// Sets the resolver to be used when this field is requested at runtime.
+ ///
+ ///
+ /// If this method is called more than once the previously set resolver will be replaced.
+ ///
+ /// The field being built.
+ /// Provide a name and this field will be declared to return a union. Use to declare union members.
+ /// The delegate to assign as the resolver. This method will be
+ /// parsed to determine input arguments for the field on the target schema.
+ /// IGraphQLFieldBuilder.
+ public static IGraphQLRuntimeTypeExtensionDefinition AddResolver(this IGraphQLRuntimeTypeExtensionDefinition fieldBuilder, string unionName, Delegate resolverMethod)
+ {
+ return AddResolverInternal(
+ fieldBuilder,
+ null as Type, // returnType
+ unionName,
+ resolverMethod);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs b/src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs
index b4064ec0f..e338d1f39 100644
--- a/src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs
+++ b/src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs
@@ -10,12 +10,10 @@
namespace GraphQL.AspNet.Configuration
{
using System;
- using System.Collections.Generic;
using GraphQL.AspNet.Common;
using GraphQL.AspNet.Common.Extensions;
using GraphQL.AspNet.Configuration.Startup;
using GraphQL.AspNet.Engine;
- using GraphQL.AspNet.Execution.Exceptions;
using GraphQL.AspNet.Interfaces.Configuration;
using GraphQL.AspNet.Interfaces.Engine;
using GraphQL.AspNet.Interfaces.Schema;
@@ -28,25 +26,6 @@ namespace GraphQL.AspNet.Configuration
///
public static class GraphQLSchemaBuilderExtensions
{
- private static readonly Dictionary SCHEMA_REGISTRATIONS;
-
- ///
- /// Initializes static members of the class.
- ///
- static GraphQLSchemaBuilderExtensions()
- {
- SCHEMA_REGISTRATIONS = new Dictionary();
- }
-
- ///
- /// Helper method to null out the schema registration references. Useful in testing and after setup is complete there is no
- /// need to keep the reference chain in tact.
- ///
- public static void Clear()
- {
- SCHEMA_REGISTRATIONS.Clear();
- }
-
///
/// Enables the query cache locally, in memory, to retain parsed query plans. When enabled, use the configuration
/// settings for each added schema to determine how each will interact with the cache. Implement your own cache provider
@@ -75,18 +54,21 @@ public static ISchemaBuilder AddGraphQL(
where TSchema : class, ISchema
{
Validation.ThrowIfNull(serviceCollection, nameof(serviceCollection));
- if (SCHEMA_REGISTRATIONS.ContainsKey(typeof(TSchema)))
+
+ var wasFound = GraphQLSchemaInjectorFactory.TryGetOrCreate(
+ out var injector,
+ serviceCollection,
+ options);
+
+ if (wasFound)
{
- throw new GraphTypeDeclarationException(
+ throw new InvalidOperationException(
$"The schema type {typeof(TSchema).FriendlyName()} has already been registered. " +
"Each schema type may only be registered once with GraphQL.");
}
- var schemaOptions = new SchemaOptions(serviceCollection);
- var injector = new GraphQLSchemaInjector(schemaOptions, options);
- SCHEMA_REGISTRATIONS.Add(typeof(TSchema), injector);
-
injector.ConfigureServices();
+
return injector.SchemaBuilder;
}
@@ -111,12 +93,15 @@ public static ISchemaBuilder AddGraphQL(
/// The application being constructed.
public static void UseGraphQL(this IApplicationBuilder app)
{
- foreach (var injector in SCHEMA_REGISTRATIONS.Values)
+ Validation.ThrowIfNull(app, nameof(app));
+ var allInjectors = app.ApplicationServices.GetServices();
+ if (allInjectors != null)
{
- injector.UseSchema(app);
+ foreach (var injector in allInjectors)
+ {
+ injector.UseSchema(app);
+ }
}
-
- Clear();
}
///
@@ -133,12 +118,15 @@ public static void UseGraphQL(this IApplicationBuilder app)
/// graphql runtime.
public static void UseGraphQL(this IServiceProvider serviceProvider)
{
- foreach (var injector in SCHEMA_REGISTRATIONS.Values)
+ Validation.ThrowIfNull(serviceProvider, nameof(serviceProvider));
+ var allInjectors = serviceProvider.GetServices();
+ if (allInjectors != null)
{
- injector.UseSchema(serviceProvider);
+ foreach (var injector in allInjectors)
+ {
+ injector.UseSchema(serviceProvider);
+ }
}
-
- Clear();
}
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/ResolverIsolationOptions.cs b/src/graphql-aspnet/Configuration/ResolverIsolationOptions.cs
index 56194be3a..d562ca47d 100644
--- a/src/graphql-aspnet/Configuration/ResolverIsolationOptions.cs
+++ b/src/graphql-aspnet/Configuration/ResolverIsolationOptions.cs
@@ -10,7 +10,7 @@
namespace GraphQL.AspNet.Configuration
{
using System;
- using GraphQL.AspNet.Internal.TypeTemplates;
+ using GraphQL.AspNet.Schemas.Generation.TypeTemplates;
///
/// A set of options indicating the various resolver types that may be
diff --git a/src/graphql-aspnet/Configuration/ResolverIsolationOptionsExtensions.cs b/src/graphql-aspnet/Configuration/ResolverIsolationOptionsExtensions.cs
index 51dc59cf2..032071a06 100644
--- a/src/graphql-aspnet/Configuration/ResolverIsolationOptionsExtensions.cs
+++ b/src/graphql-aspnet/Configuration/ResolverIsolationOptionsExtensions.cs
@@ -10,7 +10,7 @@
namespace GraphQL.AspNet.Configuration
{
using System;
- using GraphQL.AspNet.Internal.TypeTemplates;
+ using GraphQL.AspNet.Schemas.Generation.TypeTemplates;
///
/// Helper methods for working with .
diff --git a/src/graphql-aspnet/Configuration/ResolverParameterResolutionRules.cs b/src/graphql-aspnet/Configuration/ResolverParameterResolutionRules.cs
new file mode 100644
index 000000000..1ae3c2bbd
--- /dev/null
+++ b/src/graphql-aspnet/Configuration/ResolverParameterResolutionRules.cs
@@ -0,0 +1,21 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ ///
+ /// The available rules that dictate how the runtime will handle a missing or unresolved parameter
+ /// during the execution of a field or directive resolver.
+ ///
+ public enum ResolverParameterResolutionRules
+ {
+ ThrowException = 0,
+ UseNullorDefault = 1,
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/SchemaArgumentBindingRules.cs b/src/graphql-aspnet/Configuration/SchemaArgumentBindingRules.cs
new file mode 100644
index 000000000..fe8e09ce9
--- /dev/null
+++ b/src/graphql-aspnet/Configuration/SchemaArgumentBindingRules.cs
@@ -0,0 +1,60 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using System;
+
+ ///
+ /// A declaration of the possible rules used by a schema to determine which
+ /// arguments should be injected by a DI container and which should be part of the
+ /// a query.
+ ///
+ public enum SchemaArgumentBindingRules
+ {
+ ///
+ ///
+ /// Undecorated parameters will be treated as being part of the schema if the declared .NET type of the
+ /// argument is part of the schema as an appropriate graph type (e.g. SCALAR, ENUM, INPUT_OBJECT).
+ ///
+ /// Method parameters not included in the schema will attempt to be resolved from a scoped
+ /// when a field or directive resolver is invoked.
+ ///
+ ///
+ ///
+ /// This is the default option for all schemas unless changed by the developer.
+ ///
+ ParametersPreferQueryResolution = 0,
+
+ ///
+ ///
+ /// All method parameters intending to be included as arguments on the schema must be explicitly decorated using
+ /// [FromGraphQL]. Undecorated parameters will be treated as needing to be resolved
+ /// from a scoped when a field or directive resolver is invoked.
+ ///
+ ///
+ /// Undecorated parameters WILL NOT be included as part of the schema.
+ ///
+ ///
+ ParametersRequireFromGraphQLDeclaration = 1,
+
+ ///
+ ///
+ /// All method parameters intending to be resolved from a scoped
+ /// must be explicitly declared using [FromServices]. Undecorated arguments will be treated as
+ /// being part of the schema.
+ ///
+ ///
+ /// Undecorated parameters WILL be included as part of the schema. This may lead to the schema being unable to be
+ /// generated and the server failing to start.
+ ///
+ ///
+ ParametersRequireFromServicesDeclaration = 2,
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/SchemaDeclarationConfiguration.cs b/src/graphql-aspnet/Configuration/SchemaDeclarationConfiguration.cs
index fd0d5da52..73dd760e1 100644
--- a/src/graphql-aspnet/Configuration/SchemaDeclarationConfiguration.cs
+++ b/src/graphql-aspnet/Configuration/SchemaDeclarationConfiguration.cs
@@ -31,12 +31,13 @@ public SchemaDeclarationConfiguration()
this.AllowedOperations = new HashSet();
this.AllowedOperations.Add(GraphOperationType.Query);
this.AllowedOperations.Add(GraphOperationType.Mutation);
+ this.ArgumentBindingRule = SchemaArgumentBindingRules.ParametersPreferQueryResolution;
}
///
/// Merges the specified configuration setttings into this instance.
///
- /// The configuration.
+ /// The configuration values to merge into this instance.
public void Merge(ISchemaDeclarationConfiguration config)
{
if (config == null)
@@ -45,6 +46,7 @@ public void Merge(ISchemaDeclarationConfiguration config)
this.DisableIntrospection = config.DisableIntrospection;
this.FieldDeclarationRequirements = config.FieldDeclarationRequirements;
this.GraphNamingFormatter = config.GraphNamingFormatter;
+ this.ArgumentBindingRule = config.ArgumentBindingRule;
if (config.AllowedOperations != null)
{
@@ -56,6 +58,9 @@ public void Merge(ISchemaDeclarationConfiguration config)
///
public bool DisableIntrospection { get; set; }
+ ///
+ public SchemaArgumentBindingRules ArgumentBindingRule { get; set; }
+
///
public TemplateDeclarationRequirements FieldDeclarationRequirements { get; set; } = TemplateDeclarationRequirements.Default;
diff --git a/src/graphql-aspnet/Configuration/SchemaExecutionConfiguration.cs b/src/graphql-aspnet/Configuration/SchemaExecutionConfiguration.cs
index 3320b0294..f00fd9ad9 100644
--- a/src/graphql-aspnet/Configuration/SchemaExecutionConfiguration.cs
+++ b/src/graphql-aspnet/Configuration/SchemaExecutionConfiguration.cs
@@ -23,6 +23,14 @@ public class SchemaExecutionConfiguration : ISchemaExecutionConfiguration
{
private ResolverIsolationOptions _isolationOptions = ResolverIsolationOptions.None;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SchemaExecutionConfiguration()
+ {
+ this.ResolverParameterResolutionRule = ResolverParameterResolutionRules.ThrowException;
+ }
+
///
/// Merges the specified configuration setttings into this instance.
///
@@ -38,6 +46,7 @@ public void Merge(ISchemaExecutionConfiguration config)
this.MaxQueryDepth = config.MaxQueryDepth;
this.MaxQueryComplexity = config.MaxQueryComplexity;
this.DebugMode = config.DebugMode;
+ this.ResolverParameterResolutionRule = config.ResolverParameterResolutionRule;
}
///
@@ -52,6 +61,12 @@ public void Merge(ISchemaExecutionConfiguration config)
///
public float? MaxQueryComplexity { get; set; }
+ ///
+ public bool DebugMode { get; set; }
+
+ ///
+ public ResolverParameterResolutionRules ResolverParameterResolutionRule { get; set; }
+
///
public ResolverIsolationOptions ResolverIsolation
{
@@ -68,8 +83,5 @@ public ResolverIsolationOptions ResolverIsolation
_isolationOptions = value;
}
}
-
- ///
- public bool DebugMode { get; set; }
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs b/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs
index 6f9dca8d8..4cccdf188 100644
--- a/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs
+++ b/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs
@@ -209,7 +209,7 @@ public static bool IsEnumValue(this ISchemaItem schemaItem, TEnum enumVal
return schemaItem != null
&& schemaItem is IEnumValue ev
&& ev.Parent.ObjectType == typeof(TEnum)
- && Enum.Equals(ev.InternalValue, enumValue);
+ && Enum.Equals(ev.DeclaredValue, enumValue);
}
///
diff --git a/src/graphql-aspnet/Configuration/SchemaOptions.cs b/src/graphql-aspnet/Configuration/SchemaOptions.cs
index 5e09a1ed4..41ae55cef 100644
--- a/src/graphql-aspnet/Configuration/SchemaOptions.cs
+++ b/src/graphql-aspnet/Configuration/SchemaOptions.cs
@@ -20,6 +20,7 @@ namespace GraphQL.AspNet.Configuration
using GraphQL.AspNet.Directives;
using GraphQL.AspNet.Interfaces.Configuration;
using GraphQL.AspNet.Interfaces.Schema;
+ using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions;
using GraphQL.AspNet.Schemas;
using GraphQL.AspNet.Schemas.TypeSystem;
using Microsoft.Extensions.DependencyInjection;
@@ -30,11 +31,11 @@ namespace GraphQL.AspNet.Configuration
///
public abstract class SchemaOptions
{
- private readonly Dictionary _serverExtensions;
+ private readonly List _serverExtensions;
private readonly HashSet _possibleTypes;
private readonly List _registeredServices;
- private List _configExtensions;
+ private readonly List _runtimeTemplates;
///
/// Initializes a new instance of the class.
@@ -50,9 +51,9 @@ public SchemaOptions(Type schemaType, IServiceCollection serviceCollection)
Validation.ThrowIfNotCastable(schemaType, nameof(schemaType));
_possibleTypes = new HashSet(SchemaTypeToRegister.DefaultEqualityComparer);
- _serverExtensions = new Dictionary();
+ _runtimeTemplates = new List();
+ _serverExtensions = new List();
_registeredServices = new List();
- _configExtensions = new List();
this.DeclarationOptions = new SchemaDeclarationConfiguration();
this.CacheOptions = new SchemaQueryExecutionPlanCacheConfiguration();
@@ -269,20 +270,7 @@ public void RegisterExtension(TExtensionType extension)
Validation.ThrowIfNull(extension, nameof(extension));
extension.Configure(this);
- _serverExtensions.Add(extension.GetType(), extension);
- }
-
- ///
- /// Adds an extension to allow processing of schema instance by an extenal object
- /// before the schema is complete. The state of the schema is not garunteed
- /// when then extension is executed. It is highly likely that the schema will undergo
- /// further processing after the extension executes.
- ///
- /// The extension to apply.
- public void AddConfigurationExtension(ISchemaConfigurationExtension extension)
- {
- Validation.ThrowIfNull(extension, nameof(extension));
- _configExtensions.Add(extension);
+ _serverExtensions.Add(extension);
}
///
@@ -291,7 +279,7 @@ public void AddConfigurationExtension(ISchemaConfigurationExtension extension)
///
/// The type of the directive to apply.
/// IDirectiveInjector.
- public DirectiveBindingConfiguration ApplyDirective()
+ public DirectiveBindingSchemaExtension ApplyDirective()
where TDirectiveType : GraphDirective
{
return this.ApplyDirective(typeof(TDirectiveType));
@@ -303,14 +291,14 @@ public DirectiveBindingConfiguration ApplyDirective()
///
/// The type of the directive to apply to schema items.
/// IDirectiveInjector.
- public DirectiveBindingConfiguration ApplyDirective(Type directiveType)
+ public DirectiveBindingSchemaExtension ApplyDirective(Type directiveType)
{
Validation.ThrowIfNull(directiveType, nameof(directiveType));
Validation.ThrowIfNotCastable(directiveType, nameof(directiveType));
this.AddType(directiveType, null, null);
- var applicator = new DirectiveBindingConfiguration(directiveType);
- this.AddConfigurationExtension(applicator);
+ var applicator = new DirectiveBindingSchemaExtension(directiveType);
+ this.RegisterExtension(applicator);
return applicator;
}
@@ -322,11 +310,11 @@ public DirectiveBindingConfiguration ApplyDirective(Type directiveType)
///
/// Name of the directive.
/// IDirectiveInjector.
- public DirectiveBindingConfiguration ApplyDirective(string directiveName)
+ public DirectiveBindingSchemaExtension ApplyDirective(string directiveName)
{
directiveName = Validation.ThrowIfNullWhiteSpaceOrReturn(directiveName, nameof(directiveName));
- var applicator = new DirectiveBindingConfiguration(directiveName);
- this.AddConfigurationExtension(applicator);
+ var applicator = new DirectiveBindingSchemaExtension(directiveName);
+ this.RegisterExtension(applicator);
return applicator;
}
@@ -347,6 +335,30 @@ internal void FinalizeServiceRegistration()
}
}
+ ///
+ /// Adds a "runtime declared" schema item to this instance so that it can be
+ /// registered when the schema is set up.
+ ///
+ ///
+ /// "Runtime declared" schema items are synonymous with minimal api defined fields and directives.
+ ///
+ /// The schema item to include.
+ public void AddRuntimeSchemaItem(IGraphQLRuntimeSchemaItemDefinition template)
+ {
+ this.ServiceCollection.AddRuntimeFieldExecutionSupport();
+ _runtimeTemplates.Add(template);
+ }
+
+ ///
+ /// Gets the runtime configured schema item templates that need to be setup
+ /// when the schema is generated.
+ ///
+ ///
+ /// These are the templates created via the Minimal API methods.
+ ///
+ /// The runtime templates.
+ public IEnumerable RuntimeTemplates => _runtimeTemplates;
+
///
/// Gets the classes, enums, structs and other types that need to be
/// registered to the schema when its created.
@@ -354,13 +366,6 @@ internal void FinalizeServiceRegistration()
/// The registered schema types.
public IEnumerable SchemaTypesToRegister => _possibleTypes;
- ///
- /// Gets the configuration extensions that will be applied to the schema instance when its
- /// created.
- ///
- /// The configuration extensions.
- public IEnumerable ConfigurationExtensions => _configExtensions;
-
///
/// Gets or sets a value indicating whether any , or
/// any classes that implement at least one graph attribute, that are part of the entry assembly, are automatically
@@ -411,7 +416,7 @@ internal void FinalizeServiceRegistration()
/// Gets the set of options extensions added to this schema configuration.
///
/// The extensions.
- public IReadOnlyDictionary ServerExtensions => _serverExtensions;
+ public IEnumerable ServerExtensions => _serverExtensions;
///
/// Gets the service collection which contains all the required entries for
diff --git a/src/graphql-aspnet/Configuration/SchemaTypeToRegister.cs b/src/graphql-aspnet/Configuration/SchemaTypeToRegister.cs
index 1f2e7cc01..69c0f8789 100644
--- a/src/graphql-aspnet/Configuration/SchemaTypeToRegister.cs
+++ b/src/graphql-aspnet/Configuration/SchemaTypeToRegister.cs
@@ -32,7 +32,9 @@ public class SchemaTypeToRegister
/// Initializes a new instance of the class.
///
/// The type to be registered to the schema.
- /// The graph type kind to register the type as.
+ /// (optional) A specific graph type kind to force the type to be
+ /// coerced into.
+ [DebuggerStepperBoundary]
public SchemaTypeToRegister(Type type, TypeKind? typeKind = null)
{
this.Type = Validation.ThrowIfNullOrReturn(type, nameof(type));
diff --git a/src/graphql-aspnet/Configuration/ServiceCollectionExtensions.cs b/src/graphql-aspnet/Configuration/ServiceCollectionExtensions.cs
new file mode 100644
index 000000000..123a4cf8f
--- /dev/null
+++ b/src/graphql-aspnet/Configuration/ServiceCollectionExtensions.cs
@@ -0,0 +1,40 @@
+// *************************************************************
+// project: graphql-aspnet
+// --
+// repo: https://github.com/graphql-aspnet
+// docs: https://graphql-aspnet.github.io
+// --
+// License: MIT
+// *************************************************************
+
+namespace GraphQL.AspNet.Configuration
+{
+ using GraphQL.AspNet.Controllers;
+ using GraphQL.AspNet.Schemas.Generation.TypeTemplates;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.DependencyInjection.Extensions;
+
+ ///
+ /// Helper methods for configuring an
+ /// during startup.
+ ///
+ public static class ServiceCollectionExtensions
+ {
+ ///
+ /// Adds support for the execution of runtime field declarations (e.g. minimal api
+ /// defined fields).
+ ///
+ /// The service collection.
+ /// IServiceCollection.
+ public static IServiceCollection AddRuntimeFieldExecutionSupport(this IServiceCollection serviceCollection)
+ {
+ if (serviceCollection != null)
+ {
+ serviceCollection.TryAddTransient();
+ serviceCollection.TryAddTransient();
+ }
+
+ return serviceCollection;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjectorFactory.cs b/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjectorFactory.cs
index f230994c8..e4fc23977 100644
--- a/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjectorFactory.cs
+++ b/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjectorFactory.cs
@@ -10,6 +10,7 @@
namespace GraphQL.AspNet.Configuration.Startup
{
using System;
+ using System.Linq;
using GraphQL.AspNet.Common;
using GraphQL.AspNet.Interfaces.Configuration;
using GraphQL.AspNet.Interfaces.Schema;
@@ -36,11 +37,46 @@ public static ISchemaInjector Create(
where TSchema : class, ISchema
{
Validation.ThrowIfNull(schemaOptions, nameof(schemaOptions));
-
var injector = new GraphQLSchemaInjector(schemaOptions, configurationDelegate);
return injector;
}
+ ///
+ /// Creates a new schema injector capable of configuring the provided
+ /// to properly serve schema dependencies. If an injector is already registered for the target
+ /// schema it is returned directly.
+ ///
+ /// The type of the schema being created.
+ /// Will be set to the instance that was located or created.
+ /// A set of schema options to use when generating entity
+ /// templates to be injected.
+ /// A configuration delegate to configure
+ /// schema specific settings.
+ /// true if this instance was successfully retrieved, false is the instance
+ /// was newly created.
+ public static bool TryGetOrCreate(
+ out ISchemaInjector instance,
+ SchemaOptions schemaOptions,
+ Action> configurationDelegate = null)
+ where TSchema : class, ISchema
+ {
+ instance = null;
+ Validation.ThrowIfNull(schemaOptions, nameof(schemaOptions));
+
+ var registeredInjector = schemaOptions.ServiceCollection?
+ .SingleOrDefault(x => x.ImplementationInstance is ISchemaInjector)?
+ .ImplementationInstance as ISchemaInjector;
+
+ if (registeredInjector != null)
+ {
+ instance = registeredInjector;
+ return true;
+ }
+
+ instance = Create(schemaOptions, configurationDelegate);
+ return false;
+ }
+
///
/// Creates a new schema injector capable of configuring the provided
/// to properly serve schema dependencies.
@@ -61,5 +97,40 @@ public static ISchemaInjector Create(
var injector = new GraphQLSchemaInjector(schemaOptions, configurationDelegate);
return injector;
}
+
+ ///
+ /// Creates a new schema injector capable of configuring the provided
+ /// to properly serve schema dependencies. If the provided service collection already contains
+ /// a registered instance of the target injector it is returned directly.
+ ///
+ /// The type of the schema being created.
+ /// Will be set to the instance that was located or created.
+ /// The service collection to populate.
+ /// A configuration delegate to configure
+ /// schema specific settings.
+ /// true if this instance was successfully retrieved, false is the instance
+ /// was newly created.
+ public static bool TryGetOrCreate(
+ out ISchemaInjector instance,
+ IServiceCollection serviceCollection,
+ Action> configurationDelegate = null)
+ where TSchema : class, ISchema
+ {
+ instance = null;
+ Validation.ThrowIfNull(serviceCollection, nameof(serviceCollection));
+
+ var registeredInjector = serviceCollection
+ .SingleOrDefault(x => x.ImplementationInstance is ISchemaInjector)?
+ .ImplementationInstance as ISchemaInjector;
+
+ if (registeredInjector != null)
+ {
+ instance = registeredInjector;
+ return true;
+ }
+
+ instance = Create(serviceCollection, configurationDelegate);
+ return false;
+ }
}
}
\ No newline at end of file
diff --git a/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjector{TSchema}.cs b/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjector{TSchema}.cs
index 2814aae97..2ffc35b2a 100644
--- a/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjector{TSchema}.cs
+++ b/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjector{TSchema}.cs
@@ -18,6 +18,7 @@ namespace GraphQL.AspNet.Configuration.Startup
using GraphQL.AspNet.Configuration;
using GraphQL.AspNet.Engine;
using GraphQL.AspNet.Execution;
+ using GraphQL.AspNet.Execution.Exceptions;
using GraphQL.AspNet.Interfaces.Configuration;
using GraphQL.AspNet.Interfaces.Engine;
using GraphQL.AspNet.Interfaces.Execution;
@@ -85,6 +86,13 @@ public GraphQLSchemaInjector(SchemaOptions options, Action
public void ConfigureServices()
{
+ if (_options.ServiceCollection.Any(x => x.ServiceType == typeof(TSchema)))
+ {
+ throw new InvalidOperationException(
+ $"The schema type {typeof(TSchema).FriendlyName()} has already been registered. " +
+ "Each schema type may only be registered once with GraphQL.");
+ }
+
// create the builder to guide the rest of the setup operations
_configureOptions?.Invoke(_options);
@@ -139,6 +147,9 @@ public void ConfigureServices()
_options.ServiceCollection.TryAddSingleton(CreatePipelineFactory(_schemaBuilder.QueryExecutionPipeline));
_options.ServiceCollection.TryAddSingleton(CreatePipelineFactory(_schemaBuilder.DirectiveExecutionPipeline));
+ // register self for final "using" extraction
+ _options.ServiceCollection.AddSingleton(this);
+
this.RegisterEngineComponents();
_options.FinalizeServiceRegistration();
@@ -160,6 +171,7 @@ private void RegisterEngineComponents()
// "per request per schema" components
_options.ServiceCollection.TryAddTransient(typeof(IGraphQLHttpProcessor), _options.QueryHandler.HttpProcessorType);
+ _options.ServiceCollection.TryAddTransient, DefaultGraphQLSchemaFactory>();
// "per application server" instance
_options.ServiceCollection.TryAddScoped();
@@ -181,9 +193,17 @@ private void RegisterEngineComponents()
/// TSchema.
private TSchema BuildNewSchemaInstance(IServiceProvider serviceProvider)
{
- var schemaInstance = GraphSchemaBuilder.BuildSchema(serviceProvider);
- var initializer = new GraphSchemaInitializer(_options, serviceProvider);
- initializer.Initialize(schemaInstance);
+ var scope = serviceProvider.CreateScope();
+
+ var schemaConfig = _options.CreateConfiguration();
+
+ var factory = scope.ServiceProvider.GetRequiredService>();
+ var schemaInstance = factory.CreateInstance(
+ scope,
+ schemaConfig,
+ _options.SchemaTypesToRegister,
+ _options.RuntimeTemplates,
+ _options.ServerExtensions);
serviceProvider.WriteLogEntry(
(l) => l.SchemaInstanceCreated(schemaInstance));
@@ -204,7 +224,7 @@ public void UseSchema(IApplicationBuilder appBuilder)
if (_options.ServerExtensions != null)
{
foreach (var additionalOptions in _options.ServerExtensions)
- additionalOptions.Value.UseExtension(appBuilder, appBuilder.ApplicationServices);
+ additionalOptions.UseExtension(appBuilder, appBuilder.ApplicationServices);
}
if (!_options.QueryHandler.DisableDefaultRoute)
@@ -240,17 +260,13 @@ public void UseSchema(IServiceProvider serviceProvider)
/// server options on this instance are invoked with just the service provider.
private void UseSchema(IServiceProvider serviceProvider, bool invokeServerExtensions)
{
- // pre-parse any types known to this schema
- var preCacher = new SchemaPreCacher();
- preCacher.PreCacheTemplates(_options.SchemaTypesToRegister.Select(x => x.Type));
-
// only when the service provider is used for final configuration do we
// invoke extensions with just the service provider
// (mostly just for test harnessing, but may be used by developers as well)
if (invokeServerExtensions)
{
foreach (var additionalOptions in _options.ServerExtensions)
- additionalOptions.Value.UseExtension(serviceProvider: serviceProvider);
+ additionalOptions.UseExtension(serviceProvider: serviceProvider);
}
// try and build the schema
diff --git a/src/graphql-aspnet/Configuration/Startup/GraphSchemaBuilder.cs b/src/graphql-aspnet/Configuration/Startup/GraphSchemaBuilder.cs
index 4c60b8386..3e1251cea 100644
--- a/src/graphql-aspnet/Configuration/Startup/GraphSchemaBuilder.cs
+++ b/src/graphql-aspnet/Configuration/Startup/GraphSchemaBuilder.cs
@@ -42,7 +42,7 @@ public static TSchema BuildSchema(IServiceProvider sp)
where TSchema : class, ISchema
{
// the whole point of this method is to try every possible combination
- // of parameters and give out a friendly error message if nothing works.
+ // of parameters and give out a friendly error message instead of using the service provider cryptic error message.
Validation.ThrowIfNull(sp, nameof(sp));
List