diff --git a/README.md b/README.md index 7614e4b3..d1212618 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Provides the following packages: | GraphQL.Server.Transports.WebSockets | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Server.Transports.WebSockets)](https://www.nuget.org/packages/GraphQL.Server.Transports.WebSockets/) | | GraphQL.Server.Ui.Playground | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Server.Ui.Playground)](https://www.nuget.org/packages/GraphQL.Server.Ui.Playground/) | | GraphQL.Server.Ui.GraphiQL | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Server.Ui.GraphiQL)](https://www.nuget.org/packages/GraphQL.Server.Ui.GraphiQL/) | +| GraphQL.Server.Ui.Altair | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Server.Ui.Altair)](https://www.nuget.org/packages/GraphQL.Server.Ui.Altair/) | | GraphQL.Server.Ui.Voyager | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Server.Ui.Voyager)](https://www.nuget.org/packages/GraphQL.Server.Ui.Voyager/) | | GraphQL.Server.Authorization.AspNetCore | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Server.Authorization.AspNetCore)](https://www.nuget.org/packages/GraphQL.Server.Authorization.AspNetCore/) | @@ -32,6 +33,7 @@ For the WebSocket subscription protocol (depends on above) middleware: For the UI middleware/s: >`dotnet add package GraphQL.Server.Ui.GraphiQL` >`dotnet add package GraphQL.Server.Ui.Playground` +>`dotnet add package GraphQL.Server.Ui.Altair` >`dotnet add package GraphQL.Server.Ui.Voyager` @@ -68,6 +70,9 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) // use graphql-playground middleware at default url /ui/playground app.UseGraphQLPlayground(new GraphQLPlaygroundOptions()); + + // use altair middleware at default url /ui/altair + app.UseGraphQLAltair(new GraphQLAltairOptions()); // use voyager middleware at default url /ui/voyager app.UseGraphQLVoyager(new GraphQLVoyagerOptions()); diff --git a/build.cake b/build.cake index b389c426..454dfca8 100644 --- a/build.cake +++ b/build.cake @@ -13,6 +13,7 @@ var projectFiles = new [] { "./src/Transports.Subscriptions.WebSockets/Transports.Subscriptions.WebSockets.csproj", "./src/Ui.Playground/Ui.Playground.csproj", "./src/Ui.GraphiQL/Ui.GraphiQL.csproj", + "./src/Ui.Altair/Ui.Altair.csproj", "./src/Ui.Voyager/Ui.Voyager.csproj", "./src/Authorization.AspNetCore/Authorization.AspNetCore.csproj" }; diff --git a/samples/Samples.Server/Samples.Server.csproj b/samples/Samples.Server/Samples.Server.csproj index 5555fb77..2db433bd 100644 --- a/samples/Samples.Server/Samples.Server.csproj +++ b/samples/Samples.Server/Samples.Server.csproj @@ -22,6 +22,7 @@ + diff --git a/samples/Samples.Server/Startup.cs b/samples/Samples.Server/Startup.cs index f082af32..294b5e57 100644 --- a/samples/Samples.Server/Startup.cs +++ b/samples/Samples.Server/Startup.cs @@ -2,6 +2,7 @@ using GraphQL.Server; using GraphQL.Server.Ui.GraphiQL; using GraphQL.Server.Ui.Playground; +using GraphQL.Server.Ui.Altair; using GraphQL.Server.Ui.Voyager; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -98,6 +99,16 @@ public void Configure(IApplicationBuilder app) GraphQLEndPoint = "/graphql", }); + app.UseGraphQLAltair(new GraphQLAltairOptions + { + Path = "/ui/altair", + GraphQLEndPoint = "/graphql", + Headers = new Dictionary + { + ["X-api-token"] = "130fh9823bd023hd892d0j238dh", + } + }); + app.UseGraphQLVoyager(new GraphQLVoyagerOptions { Path = "/ui/voyager", diff --git a/src/Ui.Altair/AltairExtensions.cs b/src/Ui.Altair/AltairExtensions.cs new file mode 100644 index 00000000..2d121b97 --- /dev/null +++ b/src/Ui.Altair/AltairExtensions.cs @@ -0,0 +1,19 @@ +using GraphQL.Server.Ui.Altair; + +namespace Microsoft.AspNetCore.Builder +{ + /// + /// Extension methods for + /// + public static class AltairExtensions + { + /// Adds middleware for Altair GraphQL using the specified options. + /// to configure an application's request pipeline. + /// Options to customize . If not set, then the default values will be used. + /// The reference to provided instance. + public static IApplicationBuilder UseGraphQLAltair(this IApplicationBuilder app, GraphQLAltairOptions options = null) + { + return app.UseMiddleware(options ?? new GraphQLAltairOptions()); + } + } +} diff --git a/src/Ui.Altair/AltairMiddleware.cs b/src/Ui.Altair/AltairMiddleware.cs new file mode 100644 index 00000000..efba6b5e --- /dev/null +++ b/src/Ui.Altair/AltairMiddleware.cs @@ -0,0 +1,68 @@ +using GraphQL.Server.Ui.Altair.Internal; +using Microsoft.AspNetCore.Http; +using System; +using System.Text; +using System.Threading.Tasks; + +namespace GraphQL.Server.Ui.Altair +{ + /// + /// A middleware for Altair GraphQL + /// + public class AltairMiddleware + { + private readonly GraphQLAltairOptions _options; + + /// + /// The next middleware + /// + private readonly RequestDelegate _nextMiddleware; + + /// + /// The page model used to render Altair + /// + private AltairPageModel _pageModel; + + /// + /// Create a new + /// + /// The next middleware + /// Options to customize middleware + public AltairMiddleware(RequestDelegate nextMiddleware, GraphQLAltairOptions options) + { + _nextMiddleware = nextMiddleware ?? throw new ArgumentNullException(nameof(nextMiddleware)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + } + + /// + /// Try to execute the logic of the middleware + /// + /// The HttpContext + public Task Invoke(HttpContext httpContext) + { + if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); + + return IsAltairRequest(httpContext.Request) + ? InvokeAltair(httpContext.Response) + : _nextMiddleware(httpContext); + } + + private bool IsAltairRequest(HttpRequest httpRequest) + { + return HttpMethods.IsGet(httpRequest.Method) && httpRequest.Path.StartsWithSegments(_options.Path); + } + + private Task InvokeAltair(HttpResponse httpResponse) + { + httpResponse.ContentType = "text/html"; + httpResponse.StatusCode = 200; + + // Initialize page model if null + if (_pageModel == null) + _pageModel = new AltairPageModel(_options); + + var data = Encoding.UTF8.GetBytes(_pageModel.Render()); + return httpResponse.Body.WriteAsync(data, 0, data.Length); + } + } +} diff --git a/src/Ui.Altair/GraphQLAltairOptions.cs b/src/Ui.Altair/GraphQLAltairOptions.cs new file mode 100644 index 00000000..36be60c6 --- /dev/null +++ b/src/Ui.Altair/GraphQLAltairOptions.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; + +namespace GraphQL.Server.Ui.Altair +{ + /// + /// Options to customize + /// + public class GraphQLAltairOptions + { + /// + /// The Altair GraphQL EndPoint to listen + /// + public PathString Path { get; set; } = "/ui/altair"; + + /// + /// The GraphQL EndPoint + /// + public PathString GraphQLEndPoint { get; set; } = "/graphql"; + + /// + /// Altair Headers Config + /// + public Dictionary Headers { get; set; } + } +} diff --git a/src/Ui.Altair/Internal/AltairPageModel.cs b/src/Ui.Altair/Internal/AltairPageModel.cs new file mode 100644 index 00000000..b4493a17 --- /dev/null +++ b/src/Ui.Altair/Internal/AltairPageModel.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using System.IO; +using System.Text; + +namespace GraphQL.Server.Ui.Altair.Internal +{ + // https://docs.microsoft.com/en-us/aspnet/core/mvc/razor-pages/?tabs=netcore-cli + internal class AltairPageModel + { + private string _altairCSHtml; + + private readonly GraphQLAltairOptions _options; + + public AltairPageModel(GraphQLAltairOptions options) + { + _options = options; + } + + public string Render() + { + if (_altairCSHtml == null) + { + using (var manifestResourceStream = typeof(AltairPageModel).Assembly.GetManifestResourceStream("GraphQL.Server.Ui.Altair.Internal.altair.cshtml")) + { + using (var streamReader = new StreamReader(manifestResourceStream)) + { + var builder = new StringBuilder(streamReader.ReadToEnd()); + + builder.Replace("@Model.GraphQLEndPoint", _options.GraphQLEndPoint); + builder.Replace("@Model.AltairHeaders", JsonConvert.SerializeObject(_options.Headers)); + + _altairCSHtml = builder.ToString(); + } + } + } + + return _altairCSHtml; + } + } +} diff --git a/src/Ui.Altair/Internal/altair.cshtml b/src/Ui.Altair/Internal/altair.cshtml new file mode 100644 index 00000000..7ce4624f --- /dev/null +++ b/src/Ui.Altair/Internal/altair.cshtml @@ -0,0 +1,51 @@ + + + + + + Altair + + + + + + + + + +
+
+
+ Altair +
+
+ + + +
+
+
+
+ + + + + + + + diff --git a/src/Ui.Altair/Ui.Altair.csproj b/src/Ui.Altair/Ui.Altair.csproj new file mode 100644 index 00000000..8b232b70 --- /dev/null +++ b/src/Ui.Altair/Ui.Altair.csproj @@ -0,0 +1,28 @@ + + + + netcoreapp3.0;netstandard2.0 + GraphQL.Server.Ui.Altair + GraphQL.Server.Ui.Altair + Altair GraphQL extension + Altair GraphQL + GraphQL.Server.Ui.Altair + + + + + + + + + + + + + + + + + + +