ImageTagHelper
An ImageTagHelper that supports cache busting by appending a file version hash to the image src attribute [Resolves #2249] Code cleanup
This commit is contained in:
parent
a591e53dc9
commit
ab4d2eec31
|
|
@ -0,0 +1,84 @@
|
|||
// 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 System;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Mvc.TagHelpers.Internal;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.Framework.Caching.Memory;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ITagHelper"/> implementation targeting <img> elements that supports file versioning.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The tag helper won't process for cases with just the 'src' attribute.
|
||||
/// </remarks>
|
||||
[TargetElement("img", Attributes = FileVersionAttributeName + "," + SrcAttributeName)]
|
||||
public class ImageTagHelper : TagHelper
|
||||
{
|
||||
private static readonly string Namespace = typeof(ImageTagHelper).Namespace;
|
||||
|
||||
private const string FileVersionAttributeName = "asp-file-version";
|
||||
private const string SrcAttributeName = "src";
|
||||
|
||||
private FileVersionProvider _fileVersionProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Source of the image.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Passed through to the generated HTML in all cases.
|
||||
/// </remarks>
|
||||
[HtmlAttributeName(SrcAttributeName)]
|
||||
public string Src { get; set; }
|
||||
|
||||
/// <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(FileVersionAttributeName)]
|
||||
public bool FileVersion { get; set; }
|
||||
|
||||
[Activate]
|
||||
[HtmlAttributeNotBound]
|
||||
public IHostingEnvironment HostingEnvironment { get; set; }
|
||||
|
||||
[Activate]
|
||||
[HtmlAttributeNotBound]
|
||||
public ViewContext ViewContext { get; set; }
|
||||
|
||||
[Activate]
|
||||
[HtmlAttributeNotBound]
|
||||
public IMemoryCache Cache { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
if (FileVersion)
|
||||
{
|
||||
EnsureFileVersionProvider();
|
||||
output.Attributes[SrcAttributeName] = _fileVersionProvider.AddFileVersionToPath(Src);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pass through attribute that is also a well-known HTML attribute.
|
||||
output.CopyHtmlAttribute(SrcAttributeName, context);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureFileVersionProvider()
|
||||
{
|
||||
if (_fileVersionProvider == null)
|
||||
{
|
||||
_fileVersionProvider = new FileVersionProvider(
|
||||
HostingEnvironment.WebRootFileProvider,
|
||||
Cache,
|
||||
ViewContext.HttpContext.Request.PathBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +47,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
[InlineData("Link", null)]
|
||||
// Testing the ScriptTagHelper
|
||||
[InlineData("Script", null)]
|
||||
// Testing the ImageTagHelper
|
||||
[InlineData("Image", null)]
|
||||
// Testing InputTagHelper with File
|
||||
[InlineData("Input", null)]
|
||||
public async Task MvcTagHelpers_GeneratesExpectedResults(string action, string antiForgeryPath)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Image</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Image Tag Helper Test</h2>
|
||||
<!-- Plain image tag -->
|
||||
<img src="/images/red.png" alt="Red block" title="<the title>">
|
||||
|
||||
<!-- Plain image tag with file version -->
|
||||
<img alt="Red versioned" title="Red versioned" src="/images/red.png?v=W2F5D366_nQ2fQqUk3URdgWy2ZekXjHzHJaY5yaiOOk" />
|
||||
|
||||
<!-- Plain image tag with file version set to false -->
|
||||
<img alt="Red explicitly not versioned" title="Red versioned" src="/images/red.png" />
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
// 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.FileProviders;
|
||||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.Caching;
|
||||
using Microsoft.Framework.Caching.Memory;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
public class ImageTagHelperTest
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void PreservesOrderOfSourceAttributesWhenRun()
|
||||
{
|
||||
// Arrange
|
||||
var context = MakeTagHelperContext(
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "alt", new HtmlString("alt text") },
|
||||
{ "data-extra", new HtmlString("something") },
|
||||
{ "title", new HtmlString("Image title") },
|
||||
{ "src", "testimage.png" },
|
||||
{ "asp-file-version", "true" }
|
||||
});
|
||||
var output = MakeImageTagHelperOutput(
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "alt", new HtmlString("alt text") },
|
||||
{ "data-extra", new HtmlString("something") },
|
||||
{ "title", new HtmlString("Image title") },
|
||||
});
|
||||
|
||||
var expectedOutput = MakeImageTagHelperOutput(
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "alt", new HtmlString("alt text") },
|
||||
{ "data-extra", new HtmlString("something") },
|
||||
{ "title", new HtmlString("Image title") },
|
||||
{ "src", "testimage.png?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk" }
|
||||
});
|
||||
|
||||
var hostingEnvironment = MakeHostingEnvironment();
|
||||
var viewContext = MakeViewContext();
|
||||
var helper = new ImageTagHelper
|
||||
{
|
||||
HostingEnvironment = hostingEnvironment,
|
||||
ViewContext = viewContext,
|
||||
Src = "testimage.png",
|
||||
FileVersion = true,
|
||||
Cache = MakeCache(),
|
||||
};
|
||||
|
||||
// Act
|
||||
helper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedOutput.TagName, output.TagName);
|
||||
Assert.Equal(4, output.Attributes.Count);
|
||||
|
||||
for(int i=0; i < expectedOutput.Attributes.Count; i++)
|
||||
{
|
||||
var expectedAtribute = expectedOutput.Attributes[i];
|
||||
var actualAttribute = output.Attributes[i];
|
||||
Assert.Equal(expectedAtribute.Name, actualAttribute.Name);
|
||||
Assert.Equal(expectedAtribute.Value.ToString(), actualAttribute.Value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendersImageTag_AddsFileVersion()
|
||||
{
|
||||
// Arrange
|
||||
var context = MakeTagHelperContext(
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "alt", new HtmlString("Alt image text") },
|
||||
{ "src", "/images/test-image.png" },
|
||||
{ "asp-file-version", "true" }
|
||||
});
|
||||
var output = MakeImageTagHelperOutput(attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "alt", new HtmlString("Alt image text") },
|
||||
});
|
||||
var hostingEnvironment = MakeHostingEnvironment();
|
||||
var viewContext = MakeViewContext();
|
||||
var helper = new ImageTagHelper
|
||||
{
|
||||
HostingEnvironment = hostingEnvironment,
|
||||
ViewContext = viewContext,
|
||||
Src = "/images/test-image.png",
|
||||
FileVersion = true,
|
||||
Cache = MakeCache(),
|
||||
};
|
||||
|
||||
// Act
|
||||
helper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.True(output.Content.IsEmpty);
|
||||
Assert.Equal("img", output.TagName);
|
||||
Assert.Equal(2, output.Attributes.Count);
|
||||
var srcAttribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("src"));
|
||||
Assert.Equal("/images/test-image.png?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", srcAttribute.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendersImageTag_DoesNotAddFileVersion()
|
||||
{
|
||||
// Arrange
|
||||
var context = MakeTagHelperContext(
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "alt", new HtmlString("Alt image text") },
|
||||
{ "src", "/images/test-image.png" },
|
||||
{ "asp-file-version", "false" }
|
||||
});
|
||||
var output = MakeImageTagHelperOutput(attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "alt", new HtmlString("Alt image text") },
|
||||
});
|
||||
var hostingEnvironment = MakeHostingEnvironment();
|
||||
var viewContext = MakeViewContext();
|
||||
var helper = new ImageTagHelper
|
||||
{
|
||||
HostingEnvironment = hostingEnvironment,
|
||||
ViewContext = viewContext,
|
||||
Src = "/images/test-image.png",
|
||||
FileVersion = false,
|
||||
Cache = MakeCache(),
|
||||
};
|
||||
|
||||
// Act
|
||||
helper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.True(output.Content.IsEmpty);
|
||||
Assert.Equal("img", output.TagName);
|
||||
Assert.Equal(2, output.Attributes.Count);
|
||||
var srcAttribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("src"));
|
||||
Assert.Equal("/images/test-image.png", srcAttribute.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RendersImageTag_AddsFileVersion_WithRequestPathBase()
|
||||
{
|
||||
// Arrange
|
||||
var context = MakeTagHelperContext(
|
||||
attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "alt", new HtmlString("alt text") },
|
||||
{ "src", "/bar/images/image.jpg" },
|
||||
{ "asp-file-version", "true" },
|
||||
});
|
||||
var output = MakeImageTagHelperOutput(attributes: new TagHelperAttributeList
|
||||
{
|
||||
{ "alt", new HtmlString("alt text") },
|
||||
});
|
||||
var hostingEnvironment = MakeHostingEnvironment();
|
||||
var viewContext = MakeViewContext("/bar");
|
||||
var helper = new ImageTagHelper
|
||||
{
|
||||
HostingEnvironment = hostingEnvironment,
|
||||
ViewContext = viewContext,
|
||||
Src = "/bar/images/image.jpg",
|
||||
FileVersion = true,
|
||||
Cache = MakeCache(),
|
||||
};
|
||||
|
||||
// Act
|
||||
helper.Process(context, output);
|
||||
// Assert
|
||||
Assert.True(output.Content.IsEmpty);
|
||||
Assert.Equal("img", output.TagName);
|
||||
Assert.Equal(2, output.Attributes.Count);
|
||||
var srcAttribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("src"));
|
||||
Assert.Equal("/bar/images/image.jpg?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", srcAttribute.Value);
|
||||
}
|
||||
|
||||
private static ViewContext MakeViewContext(string requestPathBase = null)
|
||||
{
|
||||
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||
if (requestPathBase != null)
|
||||
{
|
||||
actionContext.HttpContext.Request.PathBase = new Http.PathString(requestPathBase);
|
||||
}
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var viewData = new ViewDataDictionary(metadataProvider);
|
||||
var viewContext = new ViewContext(
|
||||
actionContext,
|
||||
Mock.Of<IView>(),
|
||||
viewData,
|
||||
Mock.Of<ITempDataDictionary>(),
|
||||
TextWriter.Null);
|
||||
|
||||
return viewContext;
|
||||
}
|
||||
|
||||
private static TagHelperContext MakeTagHelperContext(
|
||||
TagHelperAttributeList attributes)
|
||||
{
|
||||
return new TagHelperContext(
|
||||
attributes,
|
||||
items: new Dictionary<object, object>(),
|
||||
uniqueId: Guid.NewGuid().ToString("N"),
|
||||
getChildContentAsync: () =>
|
||||
{
|
||||
var tagHelperContent = new DefaultTagHelperContent();
|
||||
tagHelperContent.SetContent(default(string));
|
||||
return Task.FromResult<TagHelperContent>(tagHelperContent);
|
||||
});
|
||||
}
|
||||
|
||||
private static TagHelperOutput MakeImageTagHelperOutput(TagHelperAttributeList attributes)
|
||||
{
|
||||
attributes = attributes ?? new TagHelperAttributeList();
|
||||
|
||||
return new TagHelperOutput("img", attributes);
|
||||
}
|
||||
|
||||
private static IHostingEnvironment MakeHostingEnvironment()
|
||||
{
|
||||
var emptyDirectoryContents = new Mock<IDirectoryContents>();
|
||||
emptyDirectoryContents.Setup(dc => dc.GetEnumerator())
|
||||
.Returns(Enumerable.Empty<IFileInfo>().GetEnumerator());
|
||||
var mockFile = new Mock<IFileInfo>();
|
||||
mockFile.SetupGet(f => f.Exists).Returns(true);
|
||||
mockFile
|
||||
.Setup(m => m.CreateReadStream())
|
||||
.Returns(() => new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")));
|
||||
var mockFileProvider = new Mock<IFileProvider>();
|
||||
mockFileProvider.Setup(fp => fp.GetDirectoryContents(It.IsAny<string>()))
|
||||
.Returns(emptyDirectoryContents.Object);
|
||||
mockFileProvider.Setup(fp => fp.GetFileInfo(It.IsAny<string>()))
|
||||
.Returns(mockFile.Object);
|
||||
var hostingEnvironment = new Mock<IHostingEnvironment>();
|
||||
hostingEnvironment.Setup(h => h.WebRootFileProvider).Returns(mockFileProvider.Object);
|
||||
|
||||
return hostingEnvironment.Object;
|
||||
}
|
||||
|
||||
private static IMemoryCache MakeCache()
|
||||
{
|
||||
object result = null;
|
||||
var cache = new Mock<IMemoryCache>();
|
||||
cache.CallBase = true;
|
||||
cache.Setup(c => c.TryGetValue(It.IsAny<string>(), It.IsAny<IEntryLink>(), out result))
|
||||
.Returns(result != null);
|
||||
|
||||
var cacheSetContext = new Mock<ICacheSetContext>();
|
||||
cacheSetContext.Setup(c => c.AddExpirationTrigger(It.IsAny<IExpirationTrigger>()));
|
||||
cache
|
||||
.Setup(
|
||||
c => c.Set(
|
||||
/*key*/ It.IsAny<string>(),
|
||||
/*link*/ It.IsAny<IEntryLink>(),
|
||||
/*state*/ It.IsAny<object>(),
|
||||
/*create*/ It.IsAny<Func<ICacheSetContext, object>>()))
|
||||
.Returns((
|
||||
string input,
|
||||
IEntryLink entryLink,
|
||||
object state,
|
||||
Func<ICacheSetContext, object> create) =>
|
||||
{
|
||||
{
|
||||
cacheSetContext.Setup(c => c.State).Returns(state);
|
||||
return create(cacheSetContext.Object);
|
||||
}
|
||||
});
|
||||
return cache.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -162,6 +162,11 @@ namespace MvcTagHelpersWebSite.Controllers
|
|||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Image()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Script()
|
||||
{
|
||||
return View();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
|
||||
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Image</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Image Tag Helper Test</h2>
|
||||
<!-- Plain image tag -->
|
||||
<img src="~/images/red.png" alt="Red block" title="<the title>">
|
||||
|
||||
<!-- Plain image tag with file version -->
|
||||
<img src="~/images/red.png" alt="Red versioned" title="Red versioned" asp-file-version="true" />
|
||||
|
||||
<!-- Plain image tag with file version set to false -->
|
||||
<img src="~/images/red.png" alt="Red explicitly not versioned" title="Red versioned" asp-file-version="false" />
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 360 B |
Loading…
Reference in New Issue