Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 GraphQL ASP.NET
Copyright (c) 2023 GraphQL ASP.NET

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
25 changes: 25 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Thanks for helping make GraphQL ASP.NET safe for everyone.

We take the security of our library seriously, including all of the open source code repositories managed through our GitHub organization.

## Reporting Security Issues

If you believe you have found a security vulnerability in any of our repositories, please report it to us through coordinated disclosure.

Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.

Instead, please send an email to security@graphql-aspnet.org

Please include as much of the information listed below as you can to help us better understand and resolve the issue:

The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting)
Full paths of source file(s) related to the manifestation of the issue if known
The location of the affected source code (tag/branch/commit or direct URL)
Any special configuration required to reproduce the issue
Step-by-step instructions to reproduce the issue
Proof-of-concept or exploit code (if possible)
Impact of the issue, including how an attacker might exploit the issue

This information will help us triage your report more quickly.

Thank you!
22 changes: 11 additions & 11 deletions docs/advanced/custom-scalars.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ sidebar_label: Custom Scalars
sidebar_position: 3
---

Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being [ENUMS](../types/enums)). When a query is resolved the returned data will be a set of nested key/value pairs where every key is a string and every value is either another set of key/value pairs, an enum or a scalar.
Scalars are the most basic, fundamental unit of content in GraphQL. It is one of two leaf types (the other being [enums](../types/enums)).

Enums, being a type of their own, are very straight forward in .NET. Scalars, however; can be anything. For instance, the `Uri` scalar is represented in GraphQL by a string. On the server though, we convert it into a `System.Uri` object, with all the extra features that go along with it.

This can be done for any value that can be represented as a simple set of characters. When you create a scalar you declare its .NET type, provide a value resolver that accepts raw data from a query (a `ReadOnlySpan<char>`) and returns the completed scalar value.
This can be done for any value that can be represented as a simple set of characters. When you create a scalar you declare its .NET type, provide a value resolver that accepts raw data from a query (a `ReadOnlySpan<char>`) and returns the instantiated scalar value.

Lets say we wanted to build a scalar called `Money` that can handle both an amount and currency symbol. We might accept it in a query like this:
Lets say we wanted to build a scalar called `Money` that can handle both an amount and currency symbol (e.g. "$23.45"). We might accept it in a query like this:

```csharp title="Declaring a Money Scalar"
public class InventoryController : GraphController
Expand Down Expand Up @@ -84,8 +84,8 @@ public interface ILeafValueResolver

### IScalarGraphType Members

- `Name`: The name of this scalar in GraphQL. This is the name that will be displayed in introspection queries.
- `InternalName`: An alternative name representing the scalar in server side code. This name is commonly used in logging messages and exceptions to identify the scalar in terms of the server. Its common to use the fully qualified name, i.e. `"MyNameSpace.Money"`.
- `Name`: The unique name of this scalar. This name must be used when declaring a variable.
- `InternalName`: An alternate name representing the scalar in server side code. This name is commonly used in logging messages and exceptions to identify the scalar in terms of the server definitions. Its common to use the fully qualified name, i.e. `"MyNameSpace.Money"`.
- `Description`: The phrase that will used to describe the scalar in introspection queries.
- `Kind`: Scalars must always be declared as `TypeKind.SCALAR`.
- `Publish`: Indicates if the scalar should be published for introspection queries. Unless there is a very strong reason not to, scalars should always be published. Set this value to `true`.
Expand All @@ -95,16 +95,16 @@ public interface ILeafValueResolver
- `OtherKnownTypes`: A collection of other potential types that could be used to represent the scalar in a controller class. For instance, integers can be expressed as `int` or `int?`. Most scalars will provide an empty list (e.g. `TypeCollection.Empty`).
- `SourceResolver`: An object that implements `ILeafValueResolver` which can convert raw input data into the scalar's primary `ObjectType`.
- `Serialize(object)`: A method that converts an instance of your scalar to a leaf value that is serializable in a query response
- This method must return a `number`, `string`, `bool` or `null`.
- When converting to a number this can be any C# number value type (int, float, decimal etc.).
- This method **must** return a `number`, `string`, `bool` or `null`.
- When converting to a number this method can return any C# number value type (int, float, decimal etc.).
- `SerializeToQueryLanguage(object)`: A method that converts an instance of your scalar to a string representing it if it were declared as part of a schema language type definition.
- This method is used when generated default values for field arguments and input object fields via introspection queries.
- This method must return a value exactly as it would appear in a schema type definition For example, strings must be surrounded by quotes.

- `ValidateObject(object)`: A method used when validating data returned from a a field resolver. GraphQL will call this method and provide an object instance to determine if its acceptable and can be used in a query result.
- `ValidateObject(object)`: A method used when validating data received from a a field resolver. GraphQL will call this method and provide an object instance to determine if its acceptable and can be used in a query result.

:::note
`ValidateObject(object)` should not attempt to enforce nullability rules. In general, all scalars "could be null" depending on their usage in a schema. All scalars should return `true` for a validation result if the provided object is `null`.
`ValidateObject(object)` should not attempt to enforce nullability rules. In general, all scalars "could be null" depending on their usage in a schema. All scalars should return `true` for a validation result if the provided object is `null`. A field's type expression, enforced by graphql, will decide if null is acceptable on an individual field.
:::

### ILeafValueResolver
Expand Down Expand Up @@ -223,7 +223,7 @@ services.AddGraphQL();
```

:::info
Since our scalar is represented by a .NET class, if we don't pre-register it GraphQL will attempt to parse the `Money` class as an input object graph type. Once registered as a scalar, any attempt to use `Money` as an object graph type will cause an exception.
Since our scalar is represented by a .NET class, if we don't pre-register it, GraphQL will attempt to parse the `Money` class as an input object graph type. Once registered as a scalar, any attempt to use `Money` as an object graph type will cause an exception.
:::

## @specifiedBy Directive
Expand Down Expand Up @@ -278,7 +278,7 @@ A few points about designing your scalar:
- Scalar types should be simple and work in isolation.
- The `ReadOnlySpan<char>` provided to `ILeafValueResolver.Resolve` should be all the data needed to generate a value, there should be no need to perform side effects or fetch additional data.
- Scalar types should not track any state, depend on any stateful objects, or attempt to use any sort of dependency injection.
- `ILeafValueResolver.Resolve` must be **FAST**! Since your resolver is used to construct an initial query plan from the raw query text, it'll be called many orders of magnitude more often than any other method.
- `ILeafValueResolver.Resolve` must be **FAST**! Since your resolver is used to construct an initial query plan from the raw query text, it'll be called many orders of magnitude more often than any other method. Taking the time and performing various micro-optimizations are appropriate for this method.

### Aim for Fewer Scalars

Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public class ToUpperDirective : GraphDirective
[DirectiveLocations(DirectiveLocation.FIELD)]
public IGraphActionResult UpdateResolver()
{
if (this.DirectiveTarget as IFieldDocumentPart fieldPart)
if (this.DirectiveTarget is IFieldDocumentPart fieldPart)
{
//
if (fieldPart.Field?.ObjectType != typeof(string))
Expand Down
27 changes: 17 additions & 10 deletions docs/advanced/graph-action-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ public class BakeryController : Controller
Donut donut = null;
// ...

// highlight-start
// return the donut and indicate success
return this.Ok(donut);
return this.Ok(donut);
// highlight-end
}
}
```
Expand All @@ -33,13 +35,15 @@ public class BakeryController : Controller
Donut donut = null;
// ...

// return the donut directly!
// highlight-start
// return the donut directly!
return donut;
// highlight-end
}
}
```

You can either return the data itself or some alternate `IActionResult` to tell ASP.NET how to render a response.
> You can either return the data itself or some alternate `IActionResult` to tell ASP.NET how to render a response.

Some common ASP.NET action results:

Expand All @@ -48,7 +52,7 @@ Some common ASP.NET action results:
- `this.File()`: Return status 200 and stream the file to the client.
- `this.View()`: Render a razor view and send the HTML to the client.

This works the same way in GraphQL ASP.NET. The available actions are slightly different (GraphQL won't stream files) but the usage is the same. You can even write your own action results.
This works the same way in GraphQL ASP.NET. The available actions are slightly different (e.g. GraphQL won't stream files) but the usage is the same. You can even write your own action results.

## Controller Action Results

Expand All @@ -57,10 +61,10 @@ Instead of `IActionResult` we use `IGraphActionResult` from a controller action
Built in Controller Action Methods:

- `this.Ok(fieldValue)` : Return _fieldValue_ as the resolved value of the field and indicate to the runtime that everything completed successfully.
- `this.Error(message)`: Indicates a problem. Child fields are not processed and an error message with the given text and error code is added to the response payload.
- `this.Error(message)`: Indicates a problem. The field will resolve to a `null` value automatically. Child fields are not processed and an error message with the given text and error code is added to the response payload.
- `this.StartBatch()` : Initiates the start a of a new batch. See [batch operations](../controllers/batch-operations.md) for details.
- `this.Unauthorized()`: Indicate the user is not authorized to request the field. A message telling them as such will be added to the result and no child fields will be processed. The field will be returned a `null` value automatically. This is sometimes necessary for data-level validation that can't be readily determined from an `[Authorize]` attribute or query level validation.
- `this.BadRequest()`: Commonly used in conjunction with `this.ModelState`. This result indicates the data supplied to the method is not valid for the operation. If given the model state collection an error for each validation error is rendered.
- `this.Unauthorized()`: Indicate the user is not authorized to request the field. A message telling them as such will be added to the result and no child fields will be processed. The field will resolve to a `null` value automatically. This is sometimes necessary for data-level validation that can't be readily determined from an `[Authorize]` attribute or query level validation.
- `this.BadRequest()`: Commonly used in conjunction with `this.ModelState`. This result indicates the data supplied to the method is not valid for the operation. If given a model state collection, an error for each validation error is rendered.
- `this.InternalServerError()`: Indicates an unintended error, such as an exception occurred. The supplied message will be added to the response and no child fields will be resolved.

### Directive Action Results
Expand Down Expand Up @@ -108,7 +112,7 @@ To create a custom result, implement `IGraphActionResult`, which defines a singl
```csharp title="IGraphActionResult.cs"
public interface IGraphActionResult
{
Task Complete(ResolutionContext context);
Task CompleteAsync(ResolutionContext context);
}
```

Expand All @@ -134,17 +138,20 @@ Looking at the `UnauthorizedGraphActionResult` is a great example of how to impl
_errorCode = errorCode ?? Constants.ErrorCodes.ACCESS_DENIED;
}

public Task Complete(ResolutionContext context)
public Task CompleteAsync(ResolutionContext context)
{
// add an error message to the response
context.Messages.Critical(
_errorMessage,
_errorCode,
context.Request.Origin);

// instruct graphql to stop processing this field
// and its children
context.Cancel();
return Task.CompletedTask;
}
}
```

The result takes in an optional error message and code, providing defaults if not supplied. Then on `Complete` it adds the message to the context and cancels its execution.
The result takes in an optional error message and code, providing defaults if not supplied. Then on `CompleteAsync` it adds the message to the context and cancels its execution.
49 changes: 28 additions & 21 deletions docs/advanced/multiple-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,61 @@ GraphQL ASP.NET supports multiple schemas on the same server out of the box. Eac
To register multiple schemas you'll need to create your own class that implements `ISchema`. While it is possible to implement `ISchema` directly, if you don't require any extra functionality in your schema its easier to just inherit from the default `GraphSchema`. Updating the `Name` and `Description` is highly encouraged as the information is referenced in several different messages and can be very helpful in debugging.

```csharp title="Declaring Custom Schemas"
// highlight-next-line
public class EmployeeSchema : GraphSchema
{
// The schema name may be referenced in some error messages
// and log entries.
public override string Name => "Employee Schema";

// The description is publically available via introspection queries.
public override string Description => "My Custom Schema";
public override string Description => "Employee Related Data";
}

// highlight-next-line
public class CustomerSchema : GraphSchema
{
public override string Name => "Customer Schema";
public override string Description => "Customer Related Data";
}
```


> Implementing `ISchema` and its dependencies from scratch is not a trivial task and is beyond the scope of this documentation.


## Register Each Schema

Each schema can be registered using an overload of `.AddGraphQL()` during startup.

```csharp title="Adding A Custom Schema at Startup"
services.AddGraphQL<EmployeeSchema>();
```
By default, the query handler will attempt to register a schema to `/graphql` as its URL. You'll want to ensure that each schema has its own endpoint by updating individual routes as necessary.

```csharp title="Adding Multiple Schemas"
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGraphQL<EmployeeSchema>((options) =>
{
// highlight-next-line
options.QueryHandler.Route = "/graphql_employees";
// add assembly or graph type references here
});

### Give Each Schema its Own HTTP Route
builder.Services.AddGraphQL<CustomerSchema>((options) =>
{
// highlight-next-line
options.QueryHandler.Route = "/graphql_customers";
// add assembly or graph type references here
});

The query handler will attempt to register a schema to `/graphql` as its URL by default; you'll want to ensure that each schema has its own endpoint by updating the individual routes.
var app = builder.Build();

```csharp title="Adding Multiple Schemas"
services.AddGraphQL<EmployeeSchema>((options) =>
{
// highlight-next-line
options.QueryHandler.Route = "/graphql_employees";
// add assembly or graph type references here
});

services.AddGraphQL<CustomerSchema>((options) =>
{
// highlight-next-line
options.QueryHandler.Route = "/graphql_customers";
// add assembly or graph type references here
});
// highlight-next-line
app.UseGraphQL();
app.Run();
```

:::note
Each schema **must** be configured to use its own endpoint.
:::

## Disable Local Graph Entity Registration

Expand Down
Loading