From be914f9a608ccc03ab699c8cd8cb3914ef0b41bb Mon Sep 17 00:00:00 2001 From: Samuel Imolorhe Date: Sun, 12 Jan 2020 01:38:56 +0100 Subject: [PATCH 1/2] Added Altair GraphQL UI. --- README.md | 5 ++ build.cake | 1 + samples/Samples.Server/Samples.Server.csproj | 1 + samples/Samples.Server/Startup.cs | 7 ++ src/Ui.Altair/AltairExtensions.cs | 19 ++++++ src/Ui.Altair/AltairGraphQLOptions.cs | 26 ++++++++ src/Ui.Altair/AltairMiddleware.cs | 68 ++++++++++++++++++++ src/Ui.Altair/Internal/AltairPageModel.cs | 40 ++++++++++++ src/Ui.Altair/Internal/altair.cshtml | 51 +++++++++++++++ src/Ui.Altair/Ui.Altair.csproj | 28 ++++++++ 10 files changed, 246 insertions(+) create mode 100644 src/Ui.Altair/AltairExtensions.cs create mode 100644 src/Ui.Altair/AltairGraphQLOptions.cs create mode 100644 src/Ui.Altair/AltairMiddleware.cs create mode 100644 src/Ui.Altair/Internal/AltairPageModel.cs create mode 100644 src/Ui.Altair/Internal/altair.cshtml create mode 100644 src/Ui.Altair/Ui.Altair.csproj diff --git a/README.md b/README.md index 7614e4b3..92ed4559 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.UseAltairGraphQL(new AltairGraphQLOptions()); // 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..90648eb2 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,12 @@ public void Configure(IApplicationBuilder app) GraphQLEndPoint = "/graphql", }); + app.UseAltairGraphQL(new AltairGraphQLOptions + { + Path = "/ui/altair", + GraphQLEndPoint = "/graphql", + }); + 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..31d9fbc0 --- /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 PlaygroundExtensions + { + /// 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 UseAltairGraphQL(this IApplicationBuilder app, AltairGraphQLOptions options = null) + { + return app.UseMiddleware(options ?? new AltairGraphQLOptions()); + } + } +} diff --git a/src/Ui.Altair/AltairGraphQLOptions.cs b/src/Ui.Altair/AltairGraphQLOptions.cs new file mode 100644 index 00000000..b587b05b --- /dev/null +++ b/src/Ui.Altair/AltairGraphQLOptions.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; + +namespace GraphQL.Server.Ui.Altair +{ + /// + /// Options to customize + /// + public class AltairGraphQLOptions + { + /// + /// The Altair GraphQL EndPoint to listen + /// + public PathString Path { get; set; } = "/ui/altair"; + + /// + /// The GraphQL EndPoint + /// + public PathString GraphQLEndPoint { get; set; } = "/graphql"; + + /// + /// The GraphQL Config + /// + public Dictionary Headers { get; set; } + } +} diff --git a/src/Ui.Altair/AltairMiddleware.cs b/src/Ui.Altair/AltairMiddleware.cs new file mode 100644 index 00000000..7ca0fdb9 --- /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 AltairGraphQLOptions _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, AltairGraphQLOptions 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/Internal/AltairPageModel.cs b/src/Ui.Altair/Internal/AltairPageModel.cs new file mode 100644 index 00000000..e7f0286a --- /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 AltairGraphQLOptions _options; + + public AltairPageModel(AltairGraphQLOptions 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 + + + + + + + + + + + + + + + + + + + From ba90136c1b964b5a081343bdfcbdaebe652a8fb2 Mon Sep 17 00:00:00 2001 From: Samuel Imolorhe Date: Sun, 12 Jan 2020 09:39:12 +0100 Subject: [PATCH 2/2] Renamed Altair classes to follow naming convention. Added Headers to sample project. --- README.md | 2 +- samples/Samples.Server/Startup.cs | 6 +++++- src/Ui.Altair/AltairExtensions.cs | 6 +++--- src/Ui.Altair/AltairMiddleware.cs | 4 ++-- .../{AltairGraphQLOptions.cs => GraphQLAltairOptions.cs} | 4 ++-- src/Ui.Altair/Internal/AltairPageModel.cs | 4 ++-- 6 files changed, 15 insertions(+), 11 deletions(-) rename src/Ui.Altair/{AltairGraphQLOptions.cs => GraphQLAltairOptions.cs} (89%) diff --git a/README.md b/README.md index 92ed4559..d1212618 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) app.UseGraphQLPlayground(new GraphQLPlaygroundOptions()); // use altair middleware at default url /ui/altair - app.UseAltairGraphQL(new AltairGraphQLOptions()); + app.UseGraphQLAltair(new GraphQLAltairOptions()); // use voyager middleware at default url /ui/voyager app.UseGraphQLVoyager(new GraphQLVoyagerOptions()); diff --git a/samples/Samples.Server/Startup.cs b/samples/Samples.Server/Startup.cs index 90648eb2..294b5e57 100644 --- a/samples/Samples.Server/Startup.cs +++ b/samples/Samples.Server/Startup.cs @@ -99,10 +99,14 @@ public void Configure(IApplicationBuilder app) GraphQLEndPoint = "/graphql", }); - app.UseAltairGraphQL(new AltairGraphQLOptions + app.UseGraphQLAltair(new GraphQLAltairOptions { Path = "/ui/altair", GraphQLEndPoint = "/graphql", + Headers = new Dictionary + { + ["X-api-token"] = "130fh9823bd023hd892d0j238dh", + } }); app.UseGraphQLVoyager(new GraphQLVoyagerOptions diff --git a/src/Ui.Altair/AltairExtensions.cs b/src/Ui.Altair/AltairExtensions.cs index 31d9fbc0..2d121b97 100644 --- a/src/Ui.Altair/AltairExtensions.cs +++ b/src/Ui.Altair/AltairExtensions.cs @@ -5,15 +5,15 @@ namespace Microsoft.AspNetCore.Builder /// /// Extension methods for /// - public static class PlaygroundExtensions + 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 UseAltairGraphQL(this IApplicationBuilder app, AltairGraphQLOptions options = null) + public static IApplicationBuilder UseGraphQLAltair(this IApplicationBuilder app, GraphQLAltairOptions options = null) { - return app.UseMiddleware(options ?? new AltairGraphQLOptions()); + return app.UseMiddleware(options ?? new GraphQLAltairOptions()); } } } diff --git a/src/Ui.Altair/AltairMiddleware.cs b/src/Ui.Altair/AltairMiddleware.cs index 7ca0fdb9..efba6b5e 100644 --- a/src/Ui.Altair/AltairMiddleware.cs +++ b/src/Ui.Altair/AltairMiddleware.cs @@ -11,7 +11,7 @@ namespace GraphQL.Server.Ui.Altair /// public class AltairMiddleware { - private readonly AltairGraphQLOptions _options; + private readonly GraphQLAltairOptions _options; /// /// The next middleware @@ -28,7 +28,7 @@ public class AltairMiddleware /// /// The next middleware /// Options to customize middleware - public AltairMiddleware(RequestDelegate nextMiddleware, AltairGraphQLOptions options) + public AltairMiddleware(RequestDelegate nextMiddleware, GraphQLAltairOptions options) { _nextMiddleware = nextMiddleware ?? throw new ArgumentNullException(nameof(nextMiddleware)); _options = options ?? throw new ArgumentNullException(nameof(options)); diff --git a/src/Ui.Altair/AltairGraphQLOptions.cs b/src/Ui.Altair/GraphQLAltairOptions.cs similarity index 89% rename from src/Ui.Altair/AltairGraphQLOptions.cs rename to src/Ui.Altair/GraphQLAltairOptions.cs index b587b05b..36be60c6 100644 --- a/src/Ui.Altair/AltairGraphQLOptions.cs +++ b/src/Ui.Altair/GraphQLAltairOptions.cs @@ -6,7 +6,7 @@ namespace GraphQL.Server.Ui.Altair /// /// Options to customize /// - public class AltairGraphQLOptions + public class GraphQLAltairOptions { /// /// The Altair GraphQL EndPoint to listen @@ -19,7 +19,7 @@ public class AltairGraphQLOptions public PathString GraphQLEndPoint { get; set; } = "/graphql"; /// - /// The GraphQL Config + /// Altair Headers Config /// public Dictionary Headers { get; set; } } diff --git a/src/Ui.Altair/Internal/AltairPageModel.cs b/src/Ui.Altair/Internal/AltairPageModel.cs index e7f0286a..b4493a17 100644 --- a/src/Ui.Altair/Internal/AltairPageModel.cs +++ b/src/Ui.Altair/Internal/AltairPageModel.cs @@ -9,9 +9,9 @@ internal class AltairPageModel { private string _altairCSHtml; - private readonly AltairGraphQLOptions _options; + private readonly GraphQLAltairOptions _options; - public AltairPageModel(AltairGraphQLOptions options) + public AltairPageModel(GraphQLAltairOptions options) { _options = options; }