From f67af550637d61226584806e0ba14e31357114e2 Mon Sep 17 00:00:00 2001
From: AFMHorizon <52022448+AFM-Horizon@users.noreply.github.com>
Date: Thu, 4 Jun 2020 03:33:18 +1000
Subject: [PATCH] Added support for CustomStrategy and Added IgnoreRoutes
Option. (#71)
[release]
---
README.md | 28 +++++++++++++++-
sample/Startup.cs | 3 +-
sample/wwwroot/customserviceworker.js | 9 +++++
src/Constants.cs | 1 +
src/PwaController.cs | 39 ++++++++++++++++------
src/PwaOptions.cs | 15 +++++++++
src/RetrieveCustomServiceworker.cs | 33 ++++++++++++++++++
src/ServiceCollectionExtensions.cs | 15 ++++++---
src/ServiceWorker/ServiceWorkerStrategy.cs | 7 +++-
9 files changed, 131 insertions(+), 19 deletions(-)
create mode 100644 sample/wwwroot/customserviceworker.js
create mode 100644 src/RetrieveCustomServiceworker.cs
diff --git a/README.md b/README.md
index 93e8242..d73a153 100644
--- a/README.md
+++ b/README.md
@@ -263,6 +263,32 @@ This strategy will always try the network first for all resources and then fall
This strategy is completely safe to use and is primarily useful for offline-only scenarios since it isn't giving any performance benefits.
+### CustomStrategy
+This strategy will allow the user to specify their own implementation as a Javascript(.js) file. By default the app will search for a file named `customserviceworker.js` in the wwwroot folder.
+A filename may be explicitly set by providing it as an option when registering the service in the `Startup.cs` or `appsettings.json` file.
+
+```C#
+public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddMvc();
+ services.AddProgressiveWebApp(new PwaOptions { RegisterServiceWorker = true, Strategy = ServiceWorkerStrategy.CustomStrategy, CustomServiceWorkerStrategyFileName = "myCustomServiceworkerStrategy.js"});
+ }
+```
+
+When creating the `customserviceworker.js` by providing {version}, {routes}, {ignoreRoutes} and {offlineRoute} values within the javascript file string, interpolation will be used to replace these values with option values as set in the `Startup.cs` or `appsettings.json` file.
+
+```javascript
+(function () {
+ //Insert Your Service Worker In place of this one!
+
+ // Update 'version' if you need to refresh the cache
+ var version = '{version}';
+ var offlineUrl = "{offlineRoute}";
+ var routes = "{routes}";
+ var routesToIgnore = "{ignoreRoutes}";
+});
+```
+
## .Net Core Application hosted as Virtual Directory
You can now specify a specific BaseURL if you plan to host your application as a Virtual Directory in IIS:
@@ -322,4 +348,4 @@ Make sure to update your `wwwroot/manifest.json` file:
## License
-[Apache 2.0](LICENSE)
\ No newline at end of file
+[Apache 2.0](LICENSE)
diff --git a/sample/Startup.cs b/sample/Startup.cs
index aeadd1a..f47903f 100644
--- a/sample/Startup.cs
+++ b/sample/Startup.cs
@@ -30,7 +30,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseBrowserLink();
}
- app.UseDeveloperExceptionPage();
+
+ app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
diff --git a/sample/wwwroot/customserviceworker.js b/sample/wwwroot/customserviceworker.js
new file mode 100644
index 0000000..db862f5
--- /dev/null
+++ b/sample/wwwroot/customserviceworker.js
@@ -0,0 +1,9 @@
+(function () {
+ //Insert Your Service Worker In place of this one!
+
+ // Update 'version' if you need to refresh the cache
+ var version = '{version}';
+ var offlineUrl = "{offlineRoute}";
+ var routes = "{routes}";
+ var routesToIgnore = "{ignoreRoutes}";
+});
\ No newline at end of file
diff --git a/src/Constants.cs b/src/Constants.cs
index 3ed9fcf..caf8863 100644
--- a/src/Constants.cs
+++ b/src/Constants.cs
@@ -3,6 +3,7 @@
internal class Constants
{
public const string ServiceworkerRoute = "/serviceworker";
+ public const string CustomServiceworkerFileName = "customserviceworker.js";
public const string Offlineroute = "/offline.html";
public const string DefaultCacheId = "v1.0";
public const string WebManifestRoute = "/manifest.webmanifest";
diff --git a/src/PwaController.cs b/src/PwaController.cs
index bb3851f..52866a7 100644
--- a/src/PwaController.cs
+++ b/src/PwaController.cs
@@ -2,8 +2,10 @@
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.CodeAnalysis.Semantics;
using Microsoft.Net.Http.Headers;
namespace WebEssentials.AspNetCore.Pwa
@@ -14,13 +16,15 @@ namespace WebEssentials.AspNetCore.Pwa
public class PwaController : Controller
{
private readonly PwaOptions _options;
+ private readonly RetrieveCustomServiceworker _customServiceworker;
///
/// Creates an instance of the controller.
///
- public PwaController(PwaOptions options)
+ public PwaController(PwaOptions options, RetrieveCustomServiceworker customServiceworker)
{
_options = options;
+ _customServiceworker = customServiceworker;
}
///
@@ -33,22 +37,35 @@ public async Task ServiceWorkerAsync()
Response.ContentType = "application/javascript; charset=utf-8";
Response.Headers[HeaderNames.CacheControl] = $"max-age={_options.ServiceWorkerCacheControlMaxAge}";
- string fileName = _options.Strategy + ".js";
- Assembly assembly = typeof(PwaController).Assembly;
- Stream resourceStream = assembly.GetManifestResourceStream($"WebEssentials.AspNetCore.Pwa.ServiceWorker.Files.{fileName}");
+ if (_options.Strategy == ServiceWorkerStrategy.CustomStrategy)
+ {
+ string js = _customServiceworker.GetCustomServiceworker(_options.CustomServiceWorkerStrategyFileName);
+ return Content(InsertStrategyOptions(js));
+ }
- using (var reader = new StreamReader(resourceStream))
+ else
{
- string js = await reader.ReadToEndAsync();
- string modified = js
- .Replace("{version}", _options.CacheId + "::" + _options.Strategy)
- .Replace("{routes}", string.Join(",", _options.RoutesToPreCache.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(r => "'" + r.Trim() + "'")))
- .Replace("{offlineRoute}", _options.BaseRoute + _options.OfflineRoute);
+ string fileName = _options.Strategy + ".js";
+ Assembly assembly = typeof(PwaController).Assembly;
+ Stream resourceStream = assembly.GetManifestResourceStream($"WebEssentials.AspNetCore.Pwa.ServiceWorker.Files.{fileName}");
- return Content(modified);
+ using (var reader = new StreamReader(resourceStream))
+ {
+ string js = await reader.ReadToEndAsync();
+ return Content(InsertStrategyOptions(js));
+ }
}
}
+ private string InsertStrategyOptions(string javascriptString)
+ {
+ return javascriptString
+ .Replace("{version}", _options.CacheId + "::" + _options.Strategy)
+ .Replace("{routes}", string.Join(",", _options.RoutesToPreCache.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(r => "'" + r.Trim() + "'")))
+ .Replace("{offlineRoute}", _options.BaseRoute + _options.OfflineRoute)
+ .Replace("{ignoreRoutes}", string.Join(",", _options.RoutesToIgnore.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(r => "'" + r.Trim() + "'")));
+ }
+
///
/// Serves the offline.html file
///
diff --git a/src/PwaOptions.cs b/src/PwaOptions.cs
index cc953a4..87dbd9e 100644
--- a/src/PwaOptions.cs
+++ b/src/PwaOptions.cs
@@ -23,6 +23,8 @@ public PwaOptions()
EnableCspNonce = false;
ServiceWorkerCacheControlMaxAge = 60 * 60 * 24 * 30; // 30 days
WebManifestCacheControlMaxAge = 60 * 60 * 24 * 30; // 30 days
+ CustomServiceWorkerStrategyFileName = Constants.CustomServiceworkerFileName;
+ RoutesToIgnore = "";
}
internal PwaOptions(IConfiguration config)
@@ -32,6 +34,9 @@ internal PwaOptions(IConfiguration config)
RoutesToPreCache = config["pwa:routesToPreCache"] ?? RoutesToPreCache;
BaseRoute = config["pwa:baseRoute"] ?? BaseRoute;
OfflineRoute = config["pwa:offlineRoute"] ?? OfflineRoute;
+ RoutesToIgnore = config["pwa:routesToIgnore"] ?? RoutesToIgnore;
+ CustomServiceWorkerStrategyFileName =
+ config["pwa:customServiceWorkerFileName"] ?? CustomServiceWorkerStrategyFileName;
if (bool.TryParse(config["pwa:registerServiceWorker"] ?? "true", out bool register))
{
@@ -118,5 +123,15 @@ internal PwaOptions(IConfiguration config)
/// Generate code even on HTTP connection. Necessary for SSL offloading.
///
public bool AllowHttp { get; set; }
+
+ ///
+ /// The file name of the Custom ServiceWorker Strategy
+ ///
+ public string CustomServiceWorkerStrategyFileName { get; set; }
+
+ ///
+ /// A comma separated list of routes to ignore when implementing a CustomServiceworker.
+ ///
+ public string RoutesToIgnore { get; set; }
}
}
diff --git a/src/RetrieveCustomServiceworker.cs b/src/RetrieveCustomServiceworker.cs
new file mode 100644
index 0000000..405774e
--- /dev/null
+++ b/src/RetrieveCustomServiceworker.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.FileProviders;
+
+namespace WebEssentials.AspNetCore.Pwa
+{
+ ///
+ /// A utility that can retrieve the contents of a CustomServiceworker strategy file
+ ///
+ public class RetrieveCustomServiceworker
+ {
+ private readonly IHostingEnvironment _env;
+
+ public RetrieveCustomServiceworker(IHostingEnvironment env)
+ {
+ _env = env;
+ }
+
+ ///
+ /// Returns a containing the contents of a Custom Serviceworker javascript file
+ ///
+ ///
+ public string GetCustomServiceworker(string fileName = "customserviceworker.js")
+ {
+ IFileInfo file = _env.WebRootFileProvider.GetFileInfo(fileName);
+ return File.ReadAllText(file.PhysicalPath);
+ }
+ }
+}
diff --git a/src/ServiceCollectionExtensions.cs b/src/ServiceCollectionExtensions.cs
index bc61537..b18c7e7 100644
--- a/src/ServiceCollectionExtensions.cs
+++ b/src/ServiceCollectionExtensions.cs
@@ -19,6 +19,7 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
{
services.TryAddSingleton();
services.AddTransient();
+ services.AddTransient();
services.AddTransient(svc => new PwaOptions(svc.GetRequiredService()));
return services;
@@ -31,6 +32,7 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
{
services.TryAddSingleton();
services.AddTransient();
+ services.AddTransient();
services.AddTransient(factory => options);
return services;
@@ -39,10 +41,11 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
///
/// Adds ServiceWorker services to the specified .
///
- public static IServiceCollection AddServiceWorker(this IServiceCollection services, string baseRoute = "", string offlineRoute = Constants.Offlineroute, ServiceWorkerStrategy strategy = ServiceWorkerStrategy.CacheFirstSafe, bool registerServiceWorker = true, bool registerWebManifest = true, string cacheId = Constants.DefaultCacheId, string routesToPreCache = "")
+ public static IServiceCollection AddServiceWorker(this IServiceCollection services, string baseRoute = "", string offlineRoute = Constants.Offlineroute, ServiceWorkerStrategy strategy = ServiceWorkerStrategy.CacheFirstSafe, bool registerServiceWorker = true, bool registerWebManifest = true, string cacheId = Constants.DefaultCacheId, string routesToPreCache = "", string routesToIgnore ="", string customServiceWorkerFileName = Constants.CustomServiceworkerFileName)
{
services.TryAddSingleton();
services.AddTransient();
+ services.AddTransient();
services.AddTransient(factory => new PwaOptions
{
BaseRoute = baseRoute,
@@ -51,7 +54,9 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
RegisterServiceWorker = registerServiceWorker,
RegisterWebmanifest = registerWebManifest,
CacheId = cacheId,
- RoutesToPreCache = routesToPreCache
+ RoutesToPreCache = routesToPreCache,
+ CustomServiceWorkerStrategyFileName = customServiceWorkerFileName,
+ RoutesToIgnore = routesToIgnore
});
return services;
@@ -61,7 +66,7 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
/// Adds Web App Manifest services to the specified .
///
/// The service collection.
- /// The path to the Web App Manifest file relative to the wwwroot rolder.
+ /// The path to the Web App Manifest file relative to the wwwroot folder.
public static IServiceCollection AddWebManifest(this IServiceCollection services, string manifestFileName = Constants.WebManifestFileName)
{
services.AddTransient();
@@ -81,7 +86,7 @@ public static IServiceCollection AddWebManifest(this IServiceCollection services
/// Adds Web App Manifest and Service Worker to the specified .
///
/// The service collection.
- /// The path to the Web App Manifest file relative to the wwwroot rolder.
+ /// The path to the Web App Manifest file relative to the wwwroot folder.
public static IServiceCollection AddProgressiveWebApp(this IServiceCollection services, string manifestFileName = Constants.WebManifestFileName)
{
return services.AddWebManifest(manifestFileName)
@@ -92,7 +97,7 @@ public static IServiceCollection AddProgressiveWebApp(this IServiceCollection se
/// Adds Web App Manifest and Service Worker to the specified .
///
/// The service collection.
- /// The path to the Web App Manifest file relative to the wwwroot rolder.
+ /// The path to the Web App Manifest file relative to the wwwroot folder.
/// Options for the service worker and Web App Manifest
public static IServiceCollection AddProgressiveWebApp(this IServiceCollection services, PwaOptions options, string manifestFileName = Constants.WebManifestFileName)
{
diff --git a/src/ServiceWorker/ServiceWorkerStrategy.cs b/src/ServiceWorker/ServiceWorkerStrategy.cs
index 4faa1ee..e5c7c85 100644
--- a/src/ServiceWorker/ServiceWorkerStrategy.cs
+++ b/src/ServiceWorker/ServiceWorkerStrategy.cs
@@ -29,6 +29,11 @@ public enum ServiceWorkerStrategy
///
/// Always tries the network first and falls back to cache when offline.
///
- NetworkFirst
+ NetworkFirst,
+
+ ///
+ /// Allows a user defined custom strategy to be provided.
+ ///
+ CustomStrategy
}
}