Skip to content

Commit f67af55

Browse files
authored
Added support for CustomStrategy and Added IgnoreRoutes Option. (#71)
[release]
1 parent 69f4125 commit f67af55

9 files changed

Lines changed: 131 additions & 19 deletions

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,32 @@ This strategy will always try the network first for all resources and then fall
263263

264264
This strategy is completely safe to use and is primarily useful for offline-only scenarios since it isn't giving any performance benefits.
265265

266+
### CustomStrategy
267+
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.
268+
A filename may be explicitly set by providing it as an option when registering the service in the `Startup.cs` or `appsettings.json` file.
269+
270+
```C#
271+
public void ConfigureServices(IServiceCollection services)
272+
{
273+
services.AddMvc();
274+
services.AddProgressiveWebApp(new PwaOptions { RegisterServiceWorker = true, Strategy = ServiceWorkerStrategy.CustomStrategy, CustomServiceWorkerStrategyFileName = "myCustomServiceworkerStrategy.js"});
275+
}
276+
```
277+
278+
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.
279+
280+
```javascript
281+
(function () {
282+
//Insert Your Service Worker In place of this one!
283+
284+
// Update 'version' if you need to refresh the cache
285+
var version = '{version}';
286+
var offlineUrl = "{offlineRoute}";
287+
var routes = "{routes}";
288+
var routesToIgnore = "{ignoreRoutes}";
289+
});
290+
```
291+
266292
## .Net Core Application hosted as Virtual Directory
267293
You can now specify a specific BaseURL if you plan to host your application as a Virtual Directory in IIS:
268294

@@ -322,4 +348,4 @@ Make sure to update your `wwwroot/manifest.json` file:
322348

323349

324350
## License
325-
[Apache 2.0](LICENSE)
351+
[Apache 2.0](LICENSE)

sample/Startup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
3030
{
3131
app.UseBrowserLink();
3232
}
33-
app.UseDeveloperExceptionPage();
33+
34+
app.UseDeveloperExceptionPage();
3435

3536
app.UseStaticFiles();
3637
app.UseMvcWithDefaultRoute();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(function () {
2+
//Insert Your Service Worker In place of this one!
3+
4+
// Update 'version' if you need to refresh the cache
5+
var version = '{version}';
6+
var offlineUrl = "{offlineRoute}";
7+
var routes = "{routes}";
8+
var routesToIgnore = "{ignoreRoutes}";
9+
});

src/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
internal class Constants
44
{
55
public const string ServiceworkerRoute = "/serviceworker";
6+
public const string CustomServiceworkerFileName = "customserviceworker.js";
67
public const string Offlineroute = "/offline.html";
78
public const string DefaultCacheId = "v1.0";
89
public const string WebManifestRoute = "/manifest.webmanifest";

src/PwaController.cs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
using System.IO;
33
using System.Linq;
44
using System.Reflection;
5+
using System.Security.Cryptography.X509Certificates;
56
using System.Threading.Tasks;
67
using Microsoft.AspNetCore.Mvc;
8+
using Microsoft.CodeAnalysis.Semantics;
79
using Microsoft.Net.Http.Headers;
810

911
namespace WebEssentials.AspNetCore.Pwa
@@ -14,13 +16,15 @@ namespace WebEssentials.AspNetCore.Pwa
1416
public class PwaController : Controller
1517
{
1618
private readonly PwaOptions _options;
19+
private readonly RetrieveCustomServiceworker _customServiceworker;
1720

1821
/// <summary>
1922
/// Creates an instance of the controller.
2023
/// </summary>
21-
public PwaController(PwaOptions options)
24+
public PwaController(PwaOptions options, RetrieveCustomServiceworker customServiceworker)
2225
{
2326
_options = options;
27+
_customServiceworker = customServiceworker;
2428
}
2529

2630
/// <summary>
@@ -33,22 +37,35 @@ public async Task<IActionResult> ServiceWorkerAsync()
3337
Response.ContentType = "application/javascript; charset=utf-8";
3438
Response.Headers[HeaderNames.CacheControl] = $"max-age={_options.ServiceWorkerCacheControlMaxAge}";
3539

36-
string fileName = _options.Strategy + ".js";
37-
Assembly assembly = typeof(PwaController).Assembly;
38-
Stream resourceStream = assembly.GetManifestResourceStream($"WebEssentials.AspNetCore.Pwa.ServiceWorker.Files.{fileName}");
40+
if (_options.Strategy == ServiceWorkerStrategy.CustomStrategy)
41+
{
42+
string js = _customServiceworker.GetCustomServiceworker(_options.CustomServiceWorkerStrategyFileName);
43+
return Content(InsertStrategyOptions(js));
44+
}
3945

40-
using (var reader = new StreamReader(resourceStream))
46+
else
4147
{
42-
string js = await reader.ReadToEndAsync();
43-
string modified = js
44-
.Replace("{version}", _options.CacheId + "::" + _options.Strategy)
45-
.Replace("{routes}", string.Join(",", _options.RoutesToPreCache.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(r => "'" + r.Trim() + "'")))
46-
.Replace("{offlineRoute}", _options.BaseRoute + _options.OfflineRoute);
48+
string fileName = _options.Strategy + ".js";
49+
Assembly assembly = typeof(PwaController).Assembly;
50+
Stream resourceStream = assembly.GetManifestResourceStream($"WebEssentials.AspNetCore.Pwa.ServiceWorker.Files.{fileName}");
4751

48-
return Content(modified);
52+
using (var reader = new StreamReader(resourceStream))
53+
{
54+
string js = await reader.ReadToEndAsync();
55+
return Content(InsertStrategyOptions(js));
56+
}
4957
}
5058
}
5159

60+
private string InsertStrategyOptions(string javascriptString)
61+
{
62+
return javascriptString
63+
.Replace("{version}", _options.CacheId + "::" + _options.Strategy)
64+
.Replace("{routes}", string.Join(",", _options.RoutesToPreCache.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(r => "'" + r.Trim() + "'")))
65+
.Replace("{offlineRoute}", _options.BaseRoute + _options.OfflineRoute)
66+
.Replace("{ignoreRoutes}", string.Join(",", _options.RoutesToIgnore.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(r => "'" + r.Trim() + "'")));
67+
}
68+
5269
/// <summary>
5370
/// Serves the offline.html file
5471
/// </summary>

src/PwaOptions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public PwaOptions()
2323
EnableCspNonce = false;
2424
ServiceWorkerCacheControlMaxAge = 60 * 60 * 24 * 30; // 30 days
2525
WebManifestCacheControlMaxAge = 60 * 60 * 24 * 30; // 30 days
26+
CustomServiceWorkerStrategyFileName = Constants.CustomServiceworkerFileName;
27+
RoutesToIgnore = "";
2628
}
2729

2830
internal PwaOptions(IConfiguration config)
@@ -32,6 +34,9 @@ internal PwaOptions(IConfiguration config)
3234
RoutesToPreCache = config["pwa:routesToPreCache"] ?? RoutesToPreCache;
3335
BaseRoute = config["pwa:baseRoute"] ?? BaseRoute;
3436
OfflineRoute = config["pwa:offlineRoute"] ?? OfflineRoute;
37+
RoutesToIgnore = config["pwa:routesToIgnore"] ?? RoutesToIgnore;
38+
CustomServiceWorkerStrategyFileName =
39+
config["pwa:customServiceWorkerFileName"] ?? CustomServiceWorkerStrategyFileName;
3540

3641
if (bool.TryParse(config["pwa:registerServiceWorker"] ?? "true", out bool register))
3742
{
@@ -118,5 +123,15 @@ internal PwaOptions(IConfiguration config)
118123
/// Generate code even on HTTP connection. Necessary for SSL offloading.
119124
/// </summary>
120125
public bool AllowHttp { get; set; }
126+
127+
/// <summary>
128+
/// The file name of the Custom ServiceWorker Strategy
129+
/// </summary>
130+
public string CustomServiceWorkerStrategyFileName { get; set; }
131+
132+
/// <summary>
133+
/// A comma separated list of routes to ignore when implementing a CustomServiceworker.
134+
/// </summary>
135+
public string RoutesToIgnore { get; set; }
121136
}
122137
}

src/RetrieveCustomServiceworker.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Text;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.Extensions.Caching.Memory;
7+
using Microsoft.Extensions.FileProviders;
8+
9+
namespace WebEssentials.AspNetCore.Pwa
10+
{
11+
/// <summary>
12+
/// A utility that can retrieve the contents of a CustomServiceworker strategy file
13+
/// </summary>
14+
public class RetrieveCustomServiceworker
15+
{
16+
private readonly IHostingEnvironment _env;
17+
18+
public RetrieveCustomServiceworker(IHostingEnvironment env)
19+
{
20+
_env = env;
21+
}
22+
23+
/// <summary>
24+
/// Returns a <seealso cref="string"/> containing the contents of a Custom Serviceworker javascript file
25+
/// </summary>
26+
/// <returns></returns>
27+
public string GetCustomServiceworker(string fileName = "customserviceworker.js")
28+
{
29+
IFileInfo file = _env.WebRootFileProvider.GetFileInfo(fileName);
30+
return File.ReadAllText(file.PhysicalPath);
31+
}
32+
}
33+
}

src/ServiceCollectionExtensions.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
1919
{
2020
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
2121
services.AddTransient<ITagHelperComponent, ServiceWorkerTagHelperComponent>();
22+
services.AddTransient<RetrieveCustomServiceworker>();
2223
services.AddTransient(svc => new PwaOptions(svc.GetRequiredService<IConfiguration>()));
2324

2425
return services;
@@ -31,6 +32,7 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
3132
{
3233
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
3334
services.AddTransient<ITagHelperComponent, ServiceWorkerTagHelperComponent>();
35+
services.AddTransient<RetrieveCustomServiceworker>();
3436
services.AddTransient(factory => options);
3537

3638
return services;
@@ -39,10 +41,11 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
3941
/// <summary>
4042
/// Adds ServiceWorker services to the specified <see cref="IServiceCollection"/>.
4143
/// </summary>
42-
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 = "")
44+
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)
4345
{
4446
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
4547
services.AddTransient<ITagHelperComponent, ServiceWorkerTagHelperComponent>();
48+
services.AddTransient<RetrieveCustomServiceworker>();
4649
services.AddTransient(factory => new PwaOptions
4750
{
4851
BaseRoute = baseRoute,
@@ -51,7 +54,9 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
5154
RegisterServiceWorker = registerServiceWorker,
5255
RegisterWebmanifest = registerWebManifest,
5356
CacheId = cacheId,
54-
RoutesToPreCache = routesToPreCache
57+
RoutesToPreCache = routesToPreCache,
58+
CustomServiceWorkerStrategyFileName = customServiceWorkerFileName,
59+
RoutesToIgnore = routesToIgnore
5560
});
5661

5762
return services;
@@ -61,7 +66,7 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
6166
/// Adds Web App Manifest services to the specified <see cref="IServiceCollection"/>.
6267
/// </summary>
6368
/// <param name="services">The service collection.</param>
64-
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot rolder.</param>
69+
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot folder.</param>
6570
public static IServiceCollection AddWebManifest(this IServiceCollection services, string manifestFileName = Constants.WebManifestFileName)
6671
{
6772
services.AddTransient<ITagHelperComponent, WebmanifestTagHelperComponent>();
@@ -81,7 +86,7 @@ public static IServiceCollection AddWebManifest(this IServiceCollection services
8186
/// Adds Web App Manifest and Service Worker to the specified <see cref="IServiceCollection"/>.
8287
/// </summary>
8388
/// <param name="services">The service collection.</param>
84-
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot rolder.</param>
89+
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot folder.</param>
8590
public static IServiceCollection AddProgressiveWebApp(this IServiceCollection services, string manifestFileName = Constants.WebManifestFileName)
8691
{
8792
return services.AddWebManifest(manifestFileName)
@@ -92,7 +97,7 @@ public static IServiceCollection AddProgressiveWebApp(this IServiceCollection se
9297
/// Adds Web App Manifest and Service Worker to the specified <see cref="IServiceCollection"/>.
9398
/// </summary>
9499
/// <param name="services">The service collection.</param>
95-
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot rolder.</param>
100+
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot folder.</param>
96101
/// <param name="options">Options for the service worker and Web App Manifest</param>
97102
public static IServiceCollection AddProgressiveWebApp(this IServiceCollection services, PwaOptions options, string manifestFileName = Constants.WebManifestFileName)
98103
{

src/ServiceWorker/ServiceWorkerStrategy.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public enum ServiceWorkerStrategy
2929
/// <summary>
3030
/// Always tries the network first and falls back to cache when offline.
3131
/// </summary>
32-
NetworkFirst
32+
NetworkFirst,
33+
34+
/// <summary>
35+
/// Allows a user defined custom strategy to be provided.
36+
/// </summary>
37+
CustomStrategy
3338
}
3439
}

0 commit comments

Comments
 (0)