Skip to content
This repository has been archived by the owner on Apr 8, 2020. It is now read-only.

Commit

Permalink
Following CR feedback, reintroduce ISpaBuilder concept
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSandersonMS committed Nov 3, 2017
1 parent 29e97bf commit 87eb919
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.AspNetCore.NodeServices.Npm;
using Microsoft.AspNetCore.NodeServices.Util;
using Microsoft.AspNetCore.SpaServices.Prerendering;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -37,22 +36,17 @@ public AngularCliBuilder(string npmScript)
}

/// <inheritdoc />
public Task Build(IApplicationBuilder app)
public Task Build(ISpaBuilder spaBuilder)
{
var spaOptions = DefaultSpaOptions.FindInPipeline(app);
if (spaOptions == null)
var sourcePath = spaBuilder.Options.SourcePath;
if (string.IsNullOrEmpty(sourcePath))
{
throw new InvalidOperationException($"{nameof(AngularCliBuilder)} can only be used in an application configured with {nameof(SpaApplicationBuilderExtensions.UseSpa)}().");
throw new InvalidOperationException($"To use {nameof(AngularCliBuilder)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
}

if (string.IsNullOrEmpty(spaOptions.SourcePath))
{
throw new InvalidOperationException($"To use {nameof(AngularCliBuilder)}, you must supply a non-empty value for the {nameof(ISpaOptions.SourcePath)} property of {nameof(ISpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
}

var logger = AngularCliMiddleware.GetOrCreateLogger(app);
var logger = AngularCliMiddleware.GetOrCreateLogger(spaBuilder.ApplicationBuilder);
var npmScriptRunner = new NpmScriptRunner(
spaOptions.SourcePath,
sourcePath,
_npmScriptName,
"--watch");
npmScriptRunner.AttachToLogger(logger);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ internal static class AngularCliMiddleware
private const int TimeoutMilliseconds = 50 * 1000;

public static void Attach(
IApplicationBuilder appBuilder,
string sourcePath,
ISpaBuilder spaBuilder,
string npmScriptName)
{
var sourcePath = spaBuilder.Options.SourcePath;
if (string.IsNullOrEmpty(sourcePath))
{
throw new ArgumentException("Cannot be null or empty", nameof(sourcePath));
Expand All @@ -37,6 +37,7 @@ internal static class AngularCliMiddleware
}

// Start Angular CLI and attach to middleware pipeline
var appBuilder = spaBuilder.ApplicationBuilder;
var logger = GetOrCreateLogger(appBuilder);
var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, npmScriptName, logger);

Expand All @@ -48,7 +49,7 @@ internal static class AngularCliMiddleware
var targetUriTask = angularCliServerInfoTask.ContinueWith(
task => new UriBuilder("http", "localhost", task.Result.Port).Uri);

SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(appBuilder, targetUriTask);
SpaProxyingExtensions.UseProxyToSpaDevelopmentServer(spaBuilder, targetUriTask);
}

internal static ILogger GetOrCreateLogger(IApplicationBuilder appBuilder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,25 @@ public static class AngularCliMiddlewareExtensions
/// This feature should only be used in development. For production deployments, be
/// sure not to enable the Angular CLI server.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
/// <param name="npmScript">The name of the script in your package.json file that launches the Angular CLI process.</param>
public static void UseAngularCliServer(
this IApplicationBuilder app,
this ISpaBuilder spaBuilder,
string npmScript)
{
if (app == null)
if (spaBuilder == null)
{
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException(nameof(spaBuilder));
}

var spaOptions = DefaultSpaOptions.FindInPipeline(app);
if (spaOptions == null)
{
throw new InvalidOperationException($"{nameof(UseAngularCliServer)} should be called inside the 'configure' callback of a call to {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
}
var spaOptions = spaBuilder.Options;

if (string.IsNullOrEmpty(spaOptions.SourcePath))
{
throw new InvalidOperationException($"To use {nameof(UseAngularCliServer)}, you must supply a non-empty value for the {nameof(ISpaOptions.SourcePath)} property of {nameof(ISpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
throw new InvalidOperationException($"To use {nameof(UseAngularCliServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}.");
}

AngularCliMiddleware.Attach(app, spaOptions.SourcePath, npmScript);
AngularCliMiddleware.Attach(spaBuilder, npmScript);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Builder;

namespace Microsoft.AspNetCore.SpaServices
{
internal class DefaultSpaBuilder : ISpaBuilder
{
public IApplicationBuilder ApplicationBuilder { get; }

public SpaOptions Options { get; }

public DefaultSpaBuilder(IApplicationBuilder applicationBuilder, string sourcePath, string urlPrefix)
{
ApplicationBuilder = applicationBuilder
?? throw new System.ArgumentNullException(nameof(applicationBuilder));

Options = new SpaOptions(sourcePath, urlPrefix);
}
}
}

This file was deleted.

25 changes: 25 additions & 0 deletions src/Microsoft.AspNetCore.SpaServices.Extensions/ISpaBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Builder;

namespace Microsoft.AspNetCore.SpaServices
{
/// <summary>
/// Defines a class that provides mechanisms for configuring the hosting
/// of a Single Page Application (SPA) and attaching middleware.
/// </summary>
public interface ISpaBuilder
{
/// <summary>
/// The <see cref="IApplicationBuilder"/> representing the middleware pipeline
/// in which the SPA is being hosted.
/// </summary>
IApplicationBuilder ApplicationBuilder { get; }

/// <summary>
/// Describes configuration options for hosting a SPA.
/// </summary>
SpaOptions Options { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public interface ISpaPrerendererBuilder
/// exists on disk. Prerendering middleware can then execute that file in
/// a Node environment.
/// </summary>
/// <param name="appBuilder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
/// <returns>A <see cref="Task"/> representing completion of the build process.</returns>
Task Build(IApplicationBuilder appBuilder);
Task Build(ISpaBuilder spaBuilder);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.SpaServices;
using Microsoft.AspNetCore.SpaServices.Prerendering;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
Expand All @@ -26,21 +26,21 @@ public static class SpaPrerenderingExtensions
/// <summary>
/// Enables server-side prerendering middleware for a Single Page Application.
/// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
/// <param name="entryPoint">The path, relative to your application root, of the JavaScript file containing prerendering logic.</param>
/// <param name="buildOnDemand">Optional. If specified, executes the supplied <see cref="ISpaPrerendererBuilder"/> before looking for the <paramref name="entryPoint"/> file. This is only intended to be used during development.</param>
/// <param name="excludeUrls">Optional. If specified, requests within these URL paths will bypass the prerenderer.</param>
/// <param name="supplyData">Optional. If specified, this callback will be invoked during prerendering, allowing you to pass additional data to the prerendering entrypoint code.</param>
public static void UseSpaPrerendering(
this IApplicationBuilder applicationBuilder,
this ISpaBuilder spaBuilder,
string entryPoint,
ISpaPrerendererBuilder buildOnDemand = null,
string[] excludeUrls = null,
Action<HttpContext, IDictionary<string, object>> supplyData = null)
{
if (applicationBuilder == null)
if (spaBuilder == null)
{
throw new ArgumentNullException(nameof(applicationBuilder));
throw new ArgumentNullException(nameof(spaBuilder));
}

if (string.IsNullOrEmpty(entryPoint))
Expand All @@ -49,9 +49,10 @@ public static class SpaPrerenderingExtensions
}

// If we're building on demand, start that process in the background now
var buildOnDemandTask = buildOnDemand?.Build(applicationBuilder);
var buildOnDemandTask = buildOnDemand?.Build(spaBuilder);

// Get all the necessary context info that will be used for each prerendering call
var applicationBuilder = spaBuilder.ApplicationBuilder;
var serviceProvider = applicationBuilder.ApplicationServices;
var nodeServices = GetNodeServices(serviceProvider);
var applicationStoppingToken = serviceProvider.GetRequiredService<IApplicationLifetime>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices;
using Microsoft.AspNetCore.SpaServices.Extensions.Proxy;
using System;
using System.Threading;
Expand All @@ -20,14 +21,14 @@ public static class SpaProxyingExtensions
/// Application (SPA) development server. This is only intended to be used during
/// development. Do not enable this middleware in production applications.
/// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
/// <param name="baseUri">The target base URI to which requests should be proxied.</param>
public static void UseProxyToSpaDevelopmentServer(
this IApplicationBuilder applicationBuilder,
this ISpaBuilder spaBuilder,
Uri baseUri)
{
UseProxyToSpaDevelopmentServer(
applicationBuilder,
spaBuilder,
Task.FromResult(baseUri));
}

Expand All @@ -36,12 +37,13 @@ public static class SpaProxyingExtensions
/// Application (SPA) development server. This is only intended to be used during
/// development. Do not enable this middleware in production applications.
/// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param>
/// <param name="spaBuilder">The <see cref="ISpaBuilder"/>.</param>
/// <param name="baseUriTask">A <see cref="Task"/> that resolves with the target base URI to which requests should be proxied.</param>
public static void UseProxyToSpaDevelopmentServer(
this IApplicationBuilder applicationBuilder,
this ISpaBuilder spaBuilder,
Task<Uri> baseUriTask)
{
var applicationBuilder = spaBuilder.ApplicationBuilder;
var applicationStoppingToken = GetStoppingToken(applicationBuilder);

// It's important not to time out the requests, as some of them might be to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static class SpaApplicationBuilderExtensions
/// of the default page that hosts your SPA user interface.
/// If not specified, the default value is <c>"index.html"</c>.
/// </param>
/// <param name="configure">
/// <param name="configuration">
/// Optional. If specified, this callback will be invoked so that additional middleware
/// can be registered within the context of this SPA.
/// </param>
Expand All @@ -49,16 +49,15 @@ public static class SpaApplicationBuilderExtensions
string urlPrefix,
string sourcePath = null,
string defaultPage = null,
Action<ISpaOptions> configure = null)
Action<ISpaBuilder> configuration = null)
{
var spaOptions = new DefaultSpaOptions(sourcePath, urlPrefix);
spaOptions.RegisterSoleInstanceInPipeline(app);
var spaBuilder = new DefaultSpaBuilder(app, sourcePath, urlPrefix);

// Invoke 'configure' to give the developer a chance to insert extra
// middleware before the 'default page' pipeline entries
configure?.Invoke(spaOptions);
configuration?.Invoke(spaBuilder);

SpaDefaultPageMiddleware.Attach(app, spaOptions);
SpaDefaultPageMiddleware.Attach(spaBuilder);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,16 @@ namespace Microsoft.AspNetCore.SpaServices
{
internal class SpaDefaultPageMiddleware
{
public static void Attach(IApplicationBuilder app, ISpaOptions spaOptions)
public static void Attach(ISpaBuilder spaBuilder)
{
if (app == null)
if (spaBuilder == null)
{
throw new ArgumentNullException(nameof(app));
throw new ArgumentNullException(nameof(spaBuilder));
}

if (spaOptions == null)
{
throw new ArgumentNullException(nameof(spaOptions));
}

var defaultPageUrl = ConstructDefaultPageUrl(spaOptions.UrlPrefix, spaOptions.DefaultPage);
var app = spaBuilder.ApplicationBuilder;
var options = spaBuilder.Options;
var defaultPageUrl = ConstructDefaultPageUrl(options.UrlPrefix, options.DefaultPage);

// Rewrite all requests to the default page
app.Use((context, next) =>
Expand Down Expand Up @@ -61,7 +58,7 @@ private static string ConstructDefaultPageUrl(string urlPrefix, string defaultPa
{
if (string.IsNullOrEmpty(defaultPage))
{
defaultPage = DefaultSpaOptions.DefaultDefaultPageValue;
defaultPage = SpaOptions.DefaultDefaultPageValue;
}

return new PathString(urlPrefix).Add(new PathString("/" + defaultPage));
Expand Down

0 comments on commit 87eb919

Please sign in to comment.