Skip to content

Commit

Permalink
Adds asp-append-version support (OrchardCMS#3581)
Browse files Browse the repository at this point in the history
* MediaFileStoreVersionProvider and FileVersionHashProvider

* WIP. Add IFileVersion to ResourceManager and Tag Helpers

* WIP Update ResourceManifest and admin layout to use asp-append-version. AppendVersion remains useful on ResourceManifest that also uses Version as when providing debug-src url's are not versioned

* create IModuleStaticFileProvider and provide registration identifiers so UseStaticFiles can resolve correct IFileProvider, in case another IFileProvider registered earlier (no conflict with media or custom registration in Cms.Web for example)

* WIP. Media ImageHelper working but needs further testing

* add ImageVersionProcessor suggest by jtkech. Tested works well

* Fixes OrchardCMS#3434 Setting the right resource key in debug mode - added OrchardCMS#8177 / OrchardCMS#8177 from Orchard V1

* add liquid tag helper for append_version

* updated LiquidFilter to work via httpContextAccessor to support media within markdown & html body

* implement cache / tidy up mediafilter

* remove IFileVersionHashProvider, and Replace, rather than remove and add the OC FileVersionProvider

* renamed MediaFileImageProvider to MediaResizingFileProvider

* add CdnBaseUrl  support for prepending a base cdn url to assets

* update documentation, and support append-version with AssertUrl Helper

* documentation tweaks

* more documentation tweaks

* add support for tenant static file provider, and change tenant static file provider manifest to once per tenant = false

* Add support for IVirtualPathBaseProvider as a better generic filter to provide additional path base information to IFileProviders if required.

* Code cleanup

* Refactoring

* Minor changes.

* Refactoring ShellFileVersionProvider

* Refactoring, rename ImageTagHelper Attribute asp-append-version

* Also use a shared cache for module static files.

* Minor change.

* Add support for AppendVersion to Link Tag Helper

* Little formatting after merging dev.
  • Loading branch information
deanmarcussen authored and sethcleaver committed Aug 12, 2019
1 parent 51e2f74 commit 4202864
Show file tree
Hide file tree
Showing 45 changed files with 697 additions and 76 deletions.
2 changes: 2 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Liquid/README.md
Expand Up @@ -251,6 +251,8 @@ Gives access to the current site settings, e.g `Site.SiteName`.
| `SuperUser` | `admin` | The user name of the site's super user. |
| `TimeZoneId` | `America/Los_Angeles` | The site's time zone id as per the tz database, c.f., https://en.wikipedia.org/wiki/List_of_tz_database_time_zones |
| `UseCdn` | `false` | Enable/disable the use of a CDN. |
| `ResourceDebugMode` | `Disabled` | Provides options for whether src or debug-src is used for loading scripts and stylesheets |
| `CdnBaseUrl` | `https://localhost:44300` | If provided a CDN Base url is prepended to local scripts and stylesheets |

### Request

Expand Down
@@ -1,4 +1,3 @@
using System;
using System.Threading.Tasks;
using Fluid;
using Fluid.Values;
Expand Down
@@ -0,0 +1,17 @@
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp.Web;
using SixLabors.ImageSharp.Web.Processors;

namespace OrchardCore.Media.Processing
{
public class ImageVersionProcessor : IImageWebProcessor
{
private static readonly IEnumerable<string> VersionCommands = new[] { "v" };

public IEnumerable<string> Commands => VersionCommands;

public FormattedImage Process(FormattedImage image, ILogger logger, IDictionary<string, string> commands)
=> image;
}
}
Expand Up @@ -18,15 +18,15 @@

namespace OrchardCore.Media.Processing
{
public class MediaFileProvider : IImageProvider
public class MediaResizingFileProvider : IImageProvider
{
public static int[] DefaultSizes = new[] { 16, 32, 50, 100, 160, 240, 480, 600, 1024, 2048 };

private readonly IMediaFileStore _mediaStore;
private readonly FormatUtilities _formatUtilities;
private readonly int[] _supportedSizes;

public MediaFileProvider(
public MediaResizingFileProvider(
IMediaFileStore mediaStore,
IOptions<ImageSharpMiddlewareOptions> options,
IShellConfiguration shellConfiguration
Expand Down Expand Up @@ -103,4 +103,4 @@ public async Task<IImageResolver> GetAsync(HttpContext context)
return new MediaFileResolver(_mediaStore, filePath, metadata);
}
}
}
}
37 changes: 36 additions & 1 deletion src/OrchardCore.Modules/OrchardCore.Media/README.md
Expand Up @@ -103,6 +103,18 @@ Stretches the resized image to fit the bounds of its container.

`<img src="~/media/animals/kittens.jpg?width=100&height=240&rmode=crop" />`

### `append_version`

Appends a version hash for an asset. Can be piped together with the other media filters.

#### Input

`{{ 'animals/kittens.jpg' | asset_url | append_version | img_tag }}`

#### Output

`<img src="~/media/animals/kittens.jpg?v=Ailxbj_jQtYc9LRXKa21DygRzmQqc3OfN1XxSaQ3UWE" />`

## Razor Helpers

To obtain the correct URL for an asset, use the `AssetUrl` helper extension method on the view's base `Orchard` property, e.g.:
Expand All @@ -113,16 +125,39 @@ To obtain the correct URL for a resized asset use `AssetUrl` with the optional w

`@Orchard.AssetUrl(Model.Paths[0], width: 100 , height: 240, resizeMode: ResizeMode.Crop)`

To append a version hash for an asset use `AssetUrl` with the append version parameter, e.g.:

`@Orchard.AssetUrl(Model.Paths[0], appendVersion: true)`

or with resizing options as well, noting that the version hash is based on the source image

`@Orchard.AssetUrl(Model.Paths[0], width: 100 , height: 240, resizeMode: ResizeMode.Crop, appendVersion: true)`

### Razor image resizing tag helpers

To use the image tag helpers add `@addTagHelper *, OrchardCore.Media` to `_ViewImports.cshtml`. `asset-src` is used to obtain the correct URL for the asset and set the `src` attribute. Width, height and resize mode can be set using `img-width`, `img-height` and `img-resize-mode` respectively. e.g.:
To use the image tag helpers add `@addTagHelper *, OrchardCore.Media` to `_ViewImports.cshtml`.

`asset-src` is used to obtain the correct URL for the asset and set the `src` attribute. Width, height and resize mode can be set using `img-width`, `img-height` and `img-resize-mode` respectively. e.g.:

`<img asset-src="Model.Paths[0]" alt="..." img-width="100" img-height="240" img-resize-mode="Crop" />`

Alternatively the Asset Url can be resolved independently and the `src` attribute used:

`<img src="@Orchard.AssetUrl(Model.Paths[0])" alt="..." img-width="100" img-height="240" img-resize-mode="Crop" />`

### Razor append version
`asp-append-version` support is available on the OrchardCore tag helpers and MVC tag helpers.

`<img asset-src="Model.Paths[0]" alt="..." asp-append-version="true" />`

Alternatively the Asset Url can be resolved independently and the `src` attribute used:

`<img src="@Orchard.AssetUrl(Model.Paths[0])" alt="..." asp-append-version="true" />`

Or when using the MVC tag helpers and the image is resolved from static assets, i.e. wwwroot

`<img src="/favicon.ico" asp-append-version="true"/>`

> The Razor Helper is accessible on the `Orchard` property if the view is using Orchard Core's Razor base class, or by injecting `OrchardCore.IOrchardHelper` in all other cases.
## Deployment Step Editor
Expand Down
@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore;
using OrchardCore.Media;
Expand All @@ -8,7 +9,7 @@ public static class OrchardRazorHelperExtensions
/// <summary>
/// Returns the relative URL of the specifier asset path with optional resizing parameters.
/// </summary>
public static string AssetUrl(this IOrchardHelper orchardHelper, string assetPath, int? width = null, int? height = null, ResizeMode resizeMode = ResizeMode.Undefined)
public static string AssetUrl(this IOrchardHelper orchardHelper, string assetPath, int? width = null, int? height = null, ResizeMode resizeMode = ResizeMode.Undefined, bool appendVersion = false)
{
var mediaFileStore = orchardHelper.HttpContext.RequestServices.GetService<IMediaFileStore>();

Expand All @@ -19,7 +20,15 @@ public static string AssetUrl(this IOrchardHelper orchardHelper, string assetPat

var resolvedAssetPath = mediaFileStore.MapPathToPublicUrl(assetPath);

return orchardHelper.ImageResizeUrl(resolvedAssetPath, width, height, resizeMode);
var resizedUrl = orchardHelper.ImageResizeUrl(resolvedAssetPath, width, height, resizeMode);

if (appendVersion)
{
var fileVersionProvider = orchardHelper.HttpContext.RequestServices.GetService<IFileVersionProvider>();

resizedUrl = fileVersionProvider.AddFileVersionToPath(orchardHelper.HttpContext.Request.PathBase, resizedUrl);
}
return resizedUrl;
}

/// <summary>
Expand Down
@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical;

namespace OrchardCore.Media.Services
{
public class MediaFileProvider : PhysicalFileProvider, IMediaFileProvider
{
public MediaFileProvider(PathString virtualPathBase, string root) : base(root)
{
VirtualPathBase = virtualPathBase;
}

public MediaFileProvider(PathString virtualPathBase, string root, ExclusionFilters filters) : base(root, filters)
{
VirtualPathBase = virtualPathBase;
}

public PathString VirtualPathBase { get; }
}
}
38 changes: 25 additions & 13 deletions src/OrchardCore.Modules/OrchardCore.Media/Startup.cs
Expand Up @@ -9,7 +9,6 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Display.ContentDisplay;
Expand All @@ -35,6 +34,7 @@
using OrchardCore.Media.TagHelpers;
using OrchardCore.Media.ViewModels;
using OrchardCore.Modules;
using OrchardCore.Modules.FileProviders;
using OrchardCore.Navigation;
using OrchardCore.Recipes;
using OrchardCore.Security.Permissions;
Expand Down Expand Up @@ -76,6 +76,25 @@ public Startup(IShellConfiguration shellConfiguration)

public override void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IMediaFileProvider>(serviceProvider =>
{
var shellOptions = serviceProvider.GetRequiredService<IOptions<ShellOptions>>();
var shellSettings = serviceProvider.GetRequiredService<ShellSettings>();
var mediaPath = GetMediaPath(shellOptions.Value, shellSettings);
if (!Directory.Exists(mediaPath))
{
Directory.CreateDirectory(mediaPath);
}
return new MediaFileProvider(AssetsRequestPath, mediaPath);
});

services.AddSingleton<IStaticFileProvider>(serviceProvider =>
{
return serviceProvider.GetRequiredService<IMediaFileProvider>();
});

services.AddSingleton<IMediaFileStore>(serviceProvider =>
{
var shellOptions = serviceProvider.GetRequiredService<IOptions<ShellOptions>>();
Expand Down Expand Up @@ -145,9 +164,10 @@ public override void ConfigureServices(IServiceCollection services)
.SetMemoryAllocator<ArrayPoolMemoryAllocator>()
.SetCache<PhysicalFileSystemCache>()
.SetCacheHash<CacheHash>()
.AddProvider<MediaFileProvider>()
.AddProvider<MediaResizingFileProvider>()
.AddProcessor<ResizeWebProcessor>()
.AddProcessor<FormatWebProcessor>()
.AddProcessor<ImageVersionProcessor>()
.AddProcessor<BackgroundColorWebProcessor>();

// Media Field
Expand All @@ -169,15 +189,7 @@ public override void ConfigureServices(IServiceCollection services)

public override void Configure(IApplicationBuilder app, IRouteBuilder routes, IServiceProvider serviceProvider)
{
var shellOptions = serviceProvider.GetRequiredService<IOptions<ShellOptions>>();
var shellSettings = serviceProvider.GetRequiredService<ShellSettings>();

var mediaPath = GetMediaPath(shellOptions.Value, shellSettings);

if (!Directory.Exists(mediaPath))
{
Directory.CreateDirectory(mediaPath);
}
var mediaFileProvider = serviceProvider.GetRequiredService<IMediaFileProvider>();

// ImageSharp before the static file provider
app.UseImageSharp();
Expand All @@ -186,7 +198,7 @@ public override void Configure(IApplicationBuilder app, IRouteBuilder routes, IS
{
// The tenant's prefix is already implied by the infrastructure
RequestPath = AssetsRequestPath,
FileProvider = new PhysicalFileProvider(mediaPath),
FileProvider = mediaFileProvider,
ServeUnknownFileTypes = true,
});
}
Expand All @@ -207,4 +219,4 @@ public override void ConfigureServices(IServiceCollection services)
services.AddScoped<IDisplayDriver<DeploymentStep>, MediaDeploymentStepDriver>();
}
}
}
}
@@ -1,3 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace OrchardCore.Media.TagHelpers
Expand All @@ -7,16 +9,37 @@ public class ImageTagHelper : TagHelper
{
private const string AssetSrcAttributeName = "asset-src";

private const string AppendVersionAttributeName = "asp-append-version";

private readonly IMediaFileStore _mediaFileStore;

private readonly IFileVersionProvider _fileVersionProvider;

private readonly IHttpContextAccessor _httpContextAccessor;

public override int Order => -10;

[HtmlAttributeName(AssetSrcAttributeName)]
public string AssetSrc { get; set; }

public ImageTagHelper(IMediaFileStore mediaFileStore)
/// <summary>
/// Value indicating if file version should be appended to the src urls.
/// </summary>
/// <remarks>
/// If <c>true</c> then a query string "v" with the encoded content of the file is added.
/// </remarks>
[HtmlAttributeName(AppendVersionAttributeName)]
public bool AppendVersion { get; set; }

public ImageTagHelper(
IMediaFileStore mediaFileStore,
IHttpContextAccessor httpContextAccessor,
IFileVersionProvider fileVersionProvider
)
{
_mediaFileStore = mediaFileStore;
_httpContextAccessor = httpContextAccessor;
_fileVersionProvider = fileVersionProvider;
}

public override void Process(TagHelperContext context, TagHelperOutput output)
Expand All @@ -28,6 +51,11 @@ public override void Process(TagHelperContext context, TagHelperOutput output)

var resolvedSrc = _mediaFileStore != null ? _mediaFileStore.MapPathToPublicUrl(AssetSrc) : AssetSrc;
output.Attributes.SetAttribute("src", resolvedSrc);

if (AppendVersion && _fileVersionProvider != null)
{
output.Attributes.SetAttribute("src", _fileVersionProvider.AddFileVersionToPath(_httpContextAccessor.HttpContext.Request.PathBase, resolvedSrc));
}
}
}
}
55 changes: 49 additions & 6 deletions src/OrchardCore.Modules/OrchardCore.Resources/README.md
Expand Up @@ -6,22 +6,44 @@ The `Resources` module provides commonly used resources like JavaScript librarie
so any module can describe what resources are necessary on any page or component. When the full page is rendered all the required
resources are computed and custom `<script>` and `<link>` tags are rendered accordingly. You can also register custom `<meta>` tags.


## Resource Settings

Resource Settings are configured through the site admin.

#### UseCdn

Enabling UseCdn will configure the `IResourceManager` to provide any scripts or styles, such as jQuery, from the configured CDN.

#### ResourceDebugMode

When enabled will serve scripts or styles, that have a CDN configured, or a debug-src, from the local server in non minified format.
This will also disabled the CdnBaseUrl prepending.

#### CdnBaseUrl

When supplied this will prepend local resources served via the `IResourceManager` or Tag Helpers with the absolute url provided. This will
be disabled in `ResourceDebugMode`

## Named Resources

Named resources are well-known scripts and stylesheets that are described in a module. They have a name, a type (script, stylesheet)
and optionally a version. The `OrchardCore.Resources` modules provides some commonly used ones:

| Name | Type | Versions | Dependencies |
| ---- | ---- | -------- | ------------ |
| jQuery | Script | 1.12.4 | - |
| jQuery | Script | 2.2.4 | - |
| jQuery | Script | 3.3.1 | - |
| Popper | Script | 1.14.3 | - |
| Bootstrap | Script | 3.4.0, 4.1.3 | jQuery, Popper |
| Bootstrap | Style | 3.4.0, 4.1.3 | - |
| jQuery.slim | Script | 3.3.1 | - |
| jQuery-ui | Script | 1.12.1 | jQuery |
| font-awesome | Style | 4.7.0, 5.5.0 | - |
| jQuery-ui-i18n | Script | 1.7.2 | jQuery-ui |
| popper | Script | 1.15.0 | - |
| bootstrap | Script | 3.4.0, 4.3.1 | jQuery, Popper |
| bootstrap | Style | 3.4.0, 4.3.1 | - |
| codemirror | Script | 5.4.2 | - |
| codemirror | Style | 5.4.2 | - |
| font-awesome | Style | 4.7.0, 5.7.2 | - |
| font-awesome | Script | 5.7.2 | - |
| font-awesome-v4-shims | Script | 5.7.2 | - |

## Usage

Expand Down Expand Up @@ -61,6 +83,15 @@ settings.UseVersion("3.3");

This will use the latest available version between `3.3` and `3.4`. If the version is not available an exception is thrown.

##### Append a version

```csharp
settings.UseAppendVersion(true);
```

This will append a version string that is calculated at runtime as an SHA256 hash of the file, the calculation cached, and
appended to the url as part of the query string, e.g. `my-script.js?v=eER9OO6zWGKaIq1RlNjImsrWN9y2oTgQKg2TrJnDUWk`

#### Register custom script

At the beginning of the HTML document:
Expand Down Expand Up @@ -134,6 +165,18 @@ the latest one is always used.
<script asp-name="bootstrap" version="3"></script>
```

##### Append a Version Hash

You can append a version hash that will be calculated, and calculation cached, and appended in the format ?v=eER9OO6zWGKaIq1RlNjImsrWN9y2oTgQKg2TrJnDUWk

```liquid
{% script name:"bootstrap", append_version:"true" %}
```

```razor
<script asp-name="bootstrap" asp-append-version="true"></script>
```

##### Specify location

By default all scripts are rendered in the footer. You can override it like this:
Expand Down

0 comments on commit 4202864

Please sign in to comment.