Skip to content

Commit

Permalink
Added support for CustomStrategy and Added IgnoreRoutes Option. (#71)
Browse files Browse the repository at this point in the history
[release]
  • Loading branch information
AFM-Horizon committed Jun 3, 2020
1 parent 69f4125 commit f67af55
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 19 deletions.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

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


## License
[Apache 2.0](LICENSE)
[Apache 2.0](LICENSE)
3 changes: 2 additions & 1 deletion sample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseBrowserLink();
}
app.UseDeveloperExceptionPage();

app.UseDeveloperExceptionPage();

app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
Expand Down
9 changes: 9 additions & 0 deletions sample/wwwroot/customserviceworker.js
Original file line number Diff line number Diff line change
@@ -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}";
});
1 change: 1 addition & 0 deletions src/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
39 changes: 28 additions & 11 deletions src/PwaController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,13 +16,15 @@ namespace WebEssentials.AspNetCore.Pwa
public class PwaController : Controller
{
private readonly PwaOptions _options;
private readonly RetrieveCustomServiceworker _customServiceworker;

/// <summary>
/// Creates an instance of the controller.
/// </summary>
public PwaController(PwaOptions options)
public PwaController(PwaOptions options, RetrieveCustomServiceworker customServiceworker)
{
_options = options;
_customServiceworker = customServiceworker;
}

/// <summary>
Expand All @@ -33,22 +37,35 @@ public async Task<IActionResult> 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() + "'")));
}

/// <summary>
/// Serves the offline.html file
/// </summary>
Expand Down
15 changes: 15 additions & 0 deletions src/PwaOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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))
{
Expand Down Expand Up @@ -118,5 +123,15 @@ internal PwaOptions(IConfiguration config)
/// Generate code even on HTTP connection. Necessary for SSL offloading.
/// </summary>
public bool AllowHttp { get; set; }

/// <summary>
/// The file name of the Custom ServiceWorker Strategy
/// </summary>
public string CustomServiceWorkerStrategyFileName { get; set; }

/// <summary>
/// A comma separated list of routes to ignore when implementing a CustomServiceworker.
/// </summary>
public string RoutesToIgnore { get; set; }
}
}
33 changes: 33 additions & 0 deletions src/RetrieveCustomServiceworker.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A utility that can retrieve the contents of a CustomServiceworker strategy file
/// </summary>
public class RetrieveCustomServiceworker
{
private readonly IHostingEnvironment _env;

public RetrieveCustomServiceworker(IHostingEnvironment env)
{
_env = env;
}

/// <summary>
/// Returns a <seealso cref="string"/> containing the contents of a Custom Serviceworker javascript file
/// </summary>
/// <returns></returns>
public string GetCustomServiceworker(string fileName = "customserviceworker.js")
{
IFileInfo file = _env.WebRootFileProvider.GetFileInfo(fileName);
return File.ReadAllText(file.PhysicalPath);
}
}
}
15 changes: 10 additions & 5 deletions src/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<ITagHelperComponent, ServiceWorkerTagHelperComponent>();
services.AddTransient<RetrieveCustomServiceworker>();
services.AddTransient(svc => new PwaOptions(svc.GetRequiredService<IConfiguration>()));

return services;
Expand All @@ -31,6 +32,7 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<ITagHelperComponent, ServiceWorkerTagHelperComponent>();
services.AddTransient<RetrieveCustomServiceworker>();
services.AddTransient(factory => options);

return services;
Expand All @@ -39,10 +41,11 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
/// <summary>
/// Adds ServiceWorker services to the specified <see cref="IServiceCollection"/>.
/// </summary>
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<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<ITagHelperComponent, ServiceWorkerTagHelperComponent>();
services.AddTransient<RetrieveCustomServiceworker>();
services.AddTransient(factory => new PwaOptions
{
BaseRoute = baseRoute,
Expand All @@ -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;
Expand All @@ -61,7 +66,7 @@ public static IServiceCollection AddServiceWorker(this IServiceCollection servic
/// Adds Web App Manifest services to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot rolder.</param>
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot folder.</param>
public static IServiceCollection AddWebManifest(this IServiceCollection services, string manifestFileName = Constants.WebManifestFileName)
{
services.AddTransient<ITagHelperComponent, WebmanifestTagHelperComponent>();
Expand All @@ -81,7 +86,7 @@ public static IServiceCollection AddWebManifest(this IServiceCollection services
/// Adds Web App Manifest and Service Worker to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot rolder.</param>
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot folder.</param>
public static IServiceCollection AddProgressiveWebApp(this IServiceCollection services, string manifestFileName = Constants.WebManifestFileName)
{
return services.AddWebManifest(manifestFileName)
Expand All @@ -92,7 +97,7 @@ public static IServiceCollection AddProgressiveWebApp(this IServiceCollection se
/// Adds Web App Manifest and Service Worker to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot rolder.</param>
/// <param name="manifestFileName">The path to the Web App Manifest file relative to the wwwroot folder.</param>
/// <param name="options">Options for the service worker and Web App Manifest</param>
public static IServiceCollection AddProgressiveWebApp(this IServiceCollection services, PwaOptions options, string manifestFileName = Constants.WebManifestFileName)
{
Expand Down
7 changes: 6 additions & 1 deletion src/ServiceWorker/ServiceWorkerStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public enum ServiceWorkerStrategy
/// <summary>
/// Always tries the network first and falls back to cache when offline.
/// </summary>
NetworkFirst
NetworkFirst,

/// <summary>
/// Allows a user defined custom strategy to be provided.
/// </summary>
CustomStrategy
}
}

0 comments on commit f67af55

Please sign in to comment.